View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl;
29  
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.net.InetAddress;
34  import java.net.Socket;
35  import java.net.SocketAddress;
36  import java.net.SocketException;
37  import java.net.SocketTimeoutException;
38  import java.nio.charset.CharsetDecoder;
39  import java.nio.charset.CharsetEncoder;
40  
41  import org.apache.http.Header;
42  import org.apache.http.HttpConnection;
43  import org.apache.http.HttpConnectionMetrics;
44  import org.apache.http.HttpEntity;
45  import org.apache.http.HttpException;
46  import org.apache.http.HttpInetConnection;
47  import org.apache.http.HttpMessage;
48  import org.apache.http.annotation.NotThreadSafe;
49  import org.apache.http.config.MessageConstraints;
50  import org.apache.http.entity.BasicHttpEntity;
51  import org.apache.http.entity.ContentLengthStrategy;
52  import org.apache.http.impl.entity.LaxContentLengthStrategy;
53  import org.apache.http.impl.entity.StrictContentLengthStrategy;
54  import org.apache.http.impl.io.ChunkedInputStream;
55  import org.apache.http.impl.io.ChunkedOutputStream;
56  import org.apache.http.impl.io.ContentLengthInputStream;
57  import org.apache.http.impl.io.ContentLengthOutputStream;
58  import org.apache.http.impl.io.HttpTransportMetricsImpl;
59  import org.apache.http.impl.io.IdentityInputStream;
60  import org.apache.http.impl.io.IdentityOutputStream;
61  import org.apache.http.impl.io.SessionInputBufferImpl;
62  import org.apache.http.impl.io.SessionOutputBufferImpl;
63  import org.apache.http.io.SessionInputBuffer;
64  import org.apache.http.io.SessionOutputBuffer;
65  import org.apache.http.protocol.HTTP;
66  import org.apache.http.util.Args;
67  import org.apache.http.util.Asserts;
68  import org.apache.http.util.NetUtils;
69  
70  /**
71   * This class serves as a base for all {@link HttpConnection} implementations and provides
72   * functionality common to both client and server HTTP connections.
73   *
74   * @since 4.0
75   */
76  @NotThreadSafe
77  public class BHttpConnectionBase implements HttpConnection, HttpInetConnection {
78  
79      private final SessionInputBufferImpl inbuffer;
80      private final SessionOutputBufferImpl outbuffer;
81      private final HttpConnectionMetricsImpl connMetrics;
82      private final ContentLengthStrategy incomingContentStrategy;
83      private final ContentLengthStrategy outgoingContentStrategy;
84  
85      private volatile boolean open;
86      private volatile Socket socket;
87  
88      /**
89       * Creates new instance of BHttpConnectionBase.
90       *
91       * @param buffersize buffer size. Must be a positive number.
92       * @param fragmentSizeHint fragment size hint.
93       * @param chardecoder decoder to be used for decoding HTTP protocol elements.
94       *   If <code>null</code> simple type cast will be used for byte to char conversion.
95       * @param charencoder encoder to be used for encoding HTTP protocol elements.
96       *   If <code>null</code> simple type cast will be used for char to byte conversion.
97       * @param constraints Message constraints. If <code>null</code>
98       *   {@link MessageConstraints#DEFAULT} will be used.
99       * @param incomingContentStrategy incoming content length strategy. If <code>null</code>
100      *   {@link LaxContentLengthStrategy#INSTANCE} will be used.
101      * @param outgoingContentStrategy outgoing content length strategy. If <code>null</code>
102      *   {@link StrictContentLengthStrategy#INSTANCE} will be used.
103      */
104     protected BHttpConnectionBase(
105             final int buffersize,
106             final int fragmentSizeHint,
107             final CharsetDecoder chardecoder,
108             final CharsetEncoder charencoder,
109             final MessageConstraints constraints,
110             final ContentLengthStrategy incomingContentStrategy,
111             final ContentLengthStrategy outgoingContentStrategy) {
112         super();
113         Args.positive(buffersize, "Buffer size");
114         final HttpTransportMetricsImpl inTransportMetrics = new HttpTransportMetricsImpl();
115         final HttpTransportMetricsImpl outTransportMetrics = new HttpTransportMetricsImpl();
116         this.inbuffer = new SessionInputBufferImpl(inTransportMetrics, buffersize, -1,
117                 constraints != null ? constraints : MessageConstraints.DEFAULT, chardecoder);
118         this.outbuffer = new SessionOutputBufferImpl(outTransportMetrics, buffersize, fragmentSizeHint,
119                 charencoder);
120         this.connMetrics = new HttpConnectionMetricsImpl(inTransportMetrics, outTransportMetrics);
121         this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy :
122             LaxContentLengthStrategy.INSTANCE;
123         this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy :
124             StrictContentLengthStrategy.INSTANCE;
125     }
126 
127     protected void ensureOpen() throws IOException {
128         Asserts.check(this.open, "Connection is not open");
129         if (!this.inbuffer.isBound()) {
130             this.inbuffer.bind(getSocketInputStream(this.socket));
131         }
132         if (!this.outbuffer.isBound()) {
133             this.outbuffer.bind(getSocketOutputStream(this.socket));
134         }
135     }
136 
137     protected InputStream getSocketInputStream(final Socket socket) throws IOException {
138         return socket.getInputStream();
139     }
140 
141     protected OutputStream getSocketOutputStream(final Socket socket) throws IOException {
142         return socket.getOutputStream();
143     }
144 
145     /**
146      * Binds this connection to the given {@link Socket}. This socket will be
147      * used by the connection to send and receive data.
148      * <p/>
149      * After this method's execution the connection status will be reported
150      * as open and the {@link #isOpen()} will return <code>true</code>.
151      *
152      * @param socket the socket.
153      * @throws IOException in case of an I/O error.
154      */
155     protected void bind(final Socket socket) throws IOException {
156         Args.notNull(socket, "Socket");
157         this.socket = socket;
158         this.open = true;
159         this.inbuffer.bind(null);
160         this.outbuffer.bind(null);
161     }
162 
163     protected SessionInputBuffer getSessionInputBuffer() {
164         return this.inbuffer;
165     }
166 
167     protected SessionOutputBuffer getSessionOutputBuffer() {
168         return this.outbuffer;
169     }
170 
171     protected void doFlush() throws IOException {
172         this.outbuffer.flush();
173     }
174 
175     public boolean isOpen() {
176         return this.open;
177     }
178 
179     protected Socket getSocket() {
180         return this.socket;
181     }
182 
183     protected OutputStream createOutputStream(
184             final long len,
185             final SessionOutputBuffer outbuffer) {
186         if (len == ContentLengthStrategy.CHUNKED) {
187             return new ChunkedOutputStream(2048, outbuffer);
188         } else if (len == ContentLengthStrategy.IDENTITY) {
189             return new IdentityOutputStream(outbuffer);
190         } else {
191             return new ContentLengthOutputStream(outbuffer, len);
192         }
193     }
194 
195     protected OutputStream prepareOutput(final HttpMessage message) throws HttpException {
196         final long len = this.outgoingContentStrategy.determineLength(message);
197         return createOutputStream(len, this.outbuffer);
198     }
199 
200     protected InputStream createInputStream(
201             final long len,
202             final SessionInputBuffer inbuffer) {
203         if (len == ContentLengthStrategy.CHUNKED) {
204             return new ChunkedInputStream(inbuffer);
205         } else if (len == ContentLengthStrategy.IDENTITY) {
206             return new IdentityInputStream(inbuffer);
207         } else {
208             return new ContentLengthInputStream(inbuffer, len);
209         }
210     }
211 
212     protected HttpEntity prepareInput(final HttpMessage message) throws HttpException {
213         final BasicHttpEntity entity = new BasicHttpEntity();
214 
215         final long len = this.incomingContentStrategy.determineLength(message);
216         final InputStream instream = createInputStream(len, this.inbuffer);
217         if (len == ContentLengthStrategy.CHUNKED) {
218             entity.setChunked(true);
219             entity.setContentLength(-1);
220             entity.setContent(instream);
221         } else if (len == ContentLengthStrategy.IDENTITY) {
222             entity.setChunked(false);
223             entity.setContentLength(-1);
224             entity.setContent(instream);
225         } else {
226             entity.setChunked(false);
227             entity.setContentLength(len);
228             entity.setContent(instream);
229         }
230 
231         final Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE);
232         if (contentTypeHeader != null) {
233             entity.setContentType(contentTypeHeader);
234         }
235         final Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING);
236         if (contentEncodingHeader != null) {
237             entity.setContentEncoding(contentEncodingHeader);
238         }
239         return entity;
240     }
241 
242     public InetAddress getLocalAddress() {
243         if (this.socket != null) {
244             return this.socket.getLocalAddress();
245         } else {
246             return null;
247         }
248     }
249 
250     public int getLocalPort() {
251         if (this.socket != null) {
252             return this.socket.getLocalPort();
253         } else {
254             return -1;
255         }
256     }
257 
258     public InetAddress getRemoteAddress() {
259         if (this.socket != null) {
260             return this.socket.getInetAddress();
261         } else {
262             return null;
263         }
264     }
265 
266     public int getRemotePort() {
267         if (this.socket != null) {
268             return this.socket.getPort();
269         } else {
270             return -1;
271         }
272     }
273 
274     public void setSocketTimeout(final int timeout) {
275         if (this.socket != null) {
276             try {
277                 this.socket.setSoTimeout(timeout);
278             } catch (final SocketException ignore) {
279                 // It is not quite clear from the Sun's documentation if there are any
280                 // other legitimate cases for a socket exception to be thrown when setting
281                 // SO_TIMEOUT besides the socket being already closed
282             }
283         }
284     }
285 
286     public int getSocketTimeout() {
287         if (this.socket != null) {
288             try {
289                 return this.socket.getSoTimeout();
290             } catch (final SocketException ignore) {
291                 return -1;
292             }
293         } else {
294             return -1;
295         }
296     }
297 
298     public void shutdown() throws IOException {
299         this.open = false;
300         final Socket tmpsocket = this.socket;
301         if (tmpsocket != null) {
302             tmpsocket.close();
303         }
304     }
305 
306     public void close() throws IOException {
307         if (!this.open) {
308             return;
309         }
310         this.open = false;
311         final Socket sock = this.socket;
312         try {
313             this.inbuffer.clear();
314             this.outbuffer.flush();
315             try {
316                 try {
317                     sock.shutdownOutput();
318                 } catch (final IOException ignore) {
319                 }
320                 try {
321                     sock.shutdownInput();
322                 } catch (final IOException ignore) {
323                 }
324             } catch (final UnsupportedOperationException ignore) {
325                 // if one isn't supported, the other one isn't either
326             }
327         } finally {
328             sock.close();
329         }
330     }
331 
332     private int fillInputBuffer(final int timeout) throws IOException {
333         final int oldtimeout = this.socket.getSoTimeout();
334         try {
335             this.socket.setSoTimeout(timeout);
336             return this.inbuffer.fillBuffer();
337         } finally {
338             this.socket.setSoTimeout(oldtimeout);
339         }
340     }
341 
342     protected boolean awaitInput(final int timeout) throws IOException {
343         if (this.inbuffer.hasBufferedData()) {
344             return true;
345         }
346         fillInputBuffer(timeout);
347         return this.inbuffer.hasBufferedData();
348     }
349 
350     public boolean isStale() {
351         if (!isOpen()) {
352             return true;
353         }
354         try {
355             final int bytesRead = fillInputBuffer(1);
356             return bytesRead < 0;
357         } catch (final SocketTimeoutException ex) {
358             return false;
359         } catch (final IOException ex) {
360             return true;
361         }
362     }
363 
364     protected void incrementRequestCount() {
365         this.connMetrics.incrementRequestCount();
366     }
367 
368     protected void incrementResponseCount() {
369         this.connMetrics.incrementResponseCount();
370     }
371 
372     public HttpConnectionMetrics getMetrics() {
373         return this.connMetrics;
374     }
375 
376     @Override
377     public String toString() {
378         if (this.socket != null) {
379             final StringBuilder buffer = new StringBuilder();
380             final SocketAddress remoteAddress = this.socket.getRemoteSocketAddress();
381             final SocketAddress localAddress = this.socket.getLocalSocketAddress();
382             if (remoteAddress != null && localAddress != null) {
383                 NetUtils.formatAddress(buffer, localAddress);
384                 buffer.append("<->");
385                 NetUtils.formatAddress(buffer, remoteAddress);
386             }
387             return buffer.toString();
388         } else {
389             return "[Not bound]";
390         }
391     }
392 
393 }