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.outbuffer.flush();
314             try {
315                 try {
316                     sock.shutdownOutput();
317                 } catch (final IOException ignore) {
318                 }
319                 try {
320                     sock.shutdownInput();
321                 } catch (final IOException ignore) {
322                 }
323             } catch (final UnsupportedOperationException ignore) {
324                 // if one isn't supported, the other one isn't either
325             }
326         } finally {
327             sock.close();
328         }
329     }
330 
331     private int fillInputBuffer(final int timeout) throws IOException {
332         final int oldtimeout = this.socket.getSoTimeout();
333         try {
334             this.socket.setSoTimeout(timeout);
335             return this.inbuffer.fillBuffer();
336         } finally {
337             this.socket.setSoTimeout(oldtimeout);
338         }
339     }
340 
341     protected boolean awaitInput(final int timeout) throws IOException {
342         if (this.inbuffer.hasBufferedData()) {
343             return true;
344         }
345         fillInputBuffer(timeout);
346         return this.inbuffer.hasBufferedData();
347     }
348 
349     public boolean isStale() {
350         if (!isOpen()) {
351             return true;
352         }
353         try {
354             final int bytesRead = fillInputBuffer(1);
355             return bytesRead < 0;
356         } catch (final SocketTimeoutException ex) {
357             return false;
358         } catch (final IOException ex) {
359             return true;
360         }
361     }
362 
363     protected void incrementRequestCount() {
364         this.connMetrics.incrementRequestCount();
365     }
366 
367     protected void incrementResponseCount() {
368         this.connMetrics.incrementResponseCount();
369     }
370 
371     public HttpConnectionMetrics getMetrics() {
372         return this.connMetrics;
373     }
374 
375     @Override
376     public String toString() {
377         if (this.socket != null) {
378             final StringBuilder buffer = new StringBuilder();
379             final SocketAddress remoteAddress = this.socket.getRemoteSocketAddress();
380             final SocketAddress localAddress = this.socket.getLocalSocketAddress();
381             if (remoteAddress != null && localAddress != null) {
382                 NetUtils.formatAddress(buffer, localAddress);
383                 buffer.append("<->");
384                 NetUtils.formatAddress(buffer, remoteAddress);
385             }
386             return buffer.toString();
387         } else {
388             return "[Not bound]";
389         }
390     }
391 
392 }