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  import java.util.concurrent.atomic.AtomicReference;
41  
42  import org.apache.http.Header;
43  import org.apache.http.HttpConnection;
44  import org.apache.http.HttpConnectionMetrics;
45  import org.apache.http.HttpEntity;
46  import org.apache.http.HttpException;
47  import org.apache.http.HttpInetConnection;
48  import org.apache.http.HttpMessage;
49  import org.apache.http.annotation.NotThreadSafe;
50  import org.apache.http.config.MessageConstraints;
51  import org.apache.http.entity.BasicHttpEntity;
52  import org.apache.http.entity.ContentLengthStrategy;
53  import org.apache.http.impl.entity.LaxContentLengthStrategy;
54  import org.apache.http.impl.entity.StrictContentLengthStrategy;
55  import org.apache.http.impl.io.ChunkedInputStream;
56  import org.apache.http.impl.io.ChunkedOutputStream;
57  import org.apache.http.impl.io.ContentLengthInputStream;
58  import org.apache.http.impl.io.ContentLengthOutputStream;
59  import org.apache.http.impl.io.HttpTransportMetricsImpl;
60  import org.apache.http.impl.io.IdentityInputStream;
61  import org.apache.http.impl.io.IdentityOutputStream;
62  import org.apache.http.impl.io.SessionInputBufferImpl;
63  import org.apache.http.impl.io.SessionOutputBufferImpl;
64  import org.apache.http.io.SessionInputBuffer;
65  import org.apache.http.io.SessionOutputBuffer;
66  import org.apache.http.protocol.HTTP;
67  import org.apache.http.util.Args;
68  import org.apache.http.util.Asserts;
69  import org.apache.http.util.NetUtils;
70  
71  /**
72   * This class serves as a base for all {@link HttpConnection} implementations and provides
73   * functionality common to both client and server HTTP connections.
74   *
75   * @since 4.0
76   */
77  @NotThreadSafe
78  public class BHttpConnectionBase implements HttpConnection, HttpInetConnection {
79  
80      private final SessionInputBufferImpl inbuffer;
81      private final SessionOutputBufferImpl outbuffer;
82      private final HttpConnectionMetricsImpl connMetrics;
83      private final ContentLengthStrategy incomingContentStrategy;
84      private final ContentLengthStrategy outgoingContentStrategy;
85      private final AtomicReference<Socket> socketHolder;
86  
87      /**
88       * Creates new instance of BHttpConnectionBase.
89       *
90       * @param buffersize buffer size. Must be a positive number.
91       * @param fragmentSizeHint fragment size hint.
92       * @param chardecoder decoder to be used for decoding HTTP protocol elements.
93       *   If <code>null</code> simple type cast will be used for byte to char conversion.
94       * @param charencoder encoder to be used for encoding HTTP protocol elements.
95       *   If <code>null</code> simple type cast will be used for char to byte conversion.
96       * @param constraints Message constraints. If <code>null</code>
97       *   {@link MessageConstraints#DEFAULT} will be used.
98       * @param incomingContentStrategy incoming content length strategy. If <code>null</code>
99       *   {@link LaxContentLengthStrategy#INSTANCE} will be used.
100      * @param outgoingContentStrategy outgoing content length strategy. If <code>null</code>
101      *   {@link StrictContentLengthStrategy#INSTANCE} will be used.
102      */
103     protected BHttpConnectionBase(
104             final int buffersize,
105             final int fragmentSizeHint,
106             final CharsetDecoder chardecoder,
107             final CharsetEncoder charencoder,
108             final MessageConstraints constraints,
109             final ContentLengthStrategy incomingContentStrategy,
110             final ContentLengthStrategy outgoingContentStrategy) {
111         super();
112         Args.positive(buffersize, "Buffer size");
113         final HttpTransportMetricsImpl inTransportMetrics = new HttpTransportMetricsImpl();
114         final HttpTransportMetricsImpl outTransportMetrics = new HttpTransportMetricsImpl();
115         this.inbuffer = new SessionInputBufferImpl(inTransportMetrics, buffersize, -1,
116                 constraints != null ? constraints : MessageConstraints.DEFAULT, chardecoder);
117         this.outbuffer = new SessionOutputBufferImpl(outTransportMetrics, buffersize, fragmentSizeHint,
118                 charencoder);
119         this.connMetrics = new HttpConnectionMetricsImpl(inTransportMetrics, outTransportMetrics);
120         this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy :
121             LaxContentLengthStrategy.INSTANCE;
122         this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy :
123             StrictContentLengthStrategy.INSTANCE;
124         this.socketHolder = new AtomicReference<Socket>();
125     }
126 
127     protected void ensureOpen() throws IOException {
128         final Socket socket = this.socketHolder.get();
129         Asserts.check(socket != null, "Connection is not open");
130         if (!this.inbuffer.isBound()) {
131             this.inbuffer.bind(getSocketInputStream(socket));
132         }
133         if (!this.outbuffer.isBound()) {
134             this.outbuffer.bind(getSocketOutputStream(socket));
135         }
136     }
137 
138     protected InputStream getSocketInputStream(final Socket socket) throws IOException {
139         return socket.getInputStream();
140     }
141 
142     protected OutputStream getSocketOutputStream(final Socket socket) throws IOException {
143         return socket.getOutputStream();
144     }
145 
146     /**
147      * Binds this connection to the given {@link Socket}. This socket will be
148      * used by the connection to send and receive data.
149      * <p/>
150      * After this method's execution the connection status will be reported
151      * as open and the {@link #isOpen()} will return <code>true</code>.
152      *
153      * @param socket the socket.
154      * @throws IOException in case of an I/O error.
155      */
156     protected void bind(final Socket socket) throws IOException {
157         Args.notNull(socket, "Socket");
158         this.socketHolder.set(socket);
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.socketHolder.get() != null;
177     }
178 
179     protected Socket getSocket() {
180         return this.socketHolder.get();
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         final Socket socket = this.socketHolder.get();
244         return socket != null ? socket.getLocalAddress() : null;
245     }
246 
247     public int getLocalPort() {
248         final Socket socket = this.socketHolder.get();
249         return socket != null ? socket.getLocalPort() : -1;
250     }
251 
252     public InetAddress getRemoteAddress() {
253         final Socket socket = this.socketHolder.get();
254         return socket != null ? socket.getInetAddress() : null;
255     }
256 
257     public int getRemotePort() {
258         final Socket socket = this.socketHolder.get();
259         return socket != null ? socket.getPort() : -1;
260     }
261 
262     public void setSocketTimeout(final int timeout) {
263         final Socket socket = this.socketHolder.get();
264         if (socket != null) {
265             try {
266                 socket.setSoTimeout(timeout);
267             } catch (final SocketException ignore) {
268                 // It is not quite clear from the Sun's documentation if there are any
269                 // other legitimate cases for a socket exception to be thrown when setting
270                 // SO_TIMEOUT besides the socket being already closed
271             }
272         }
273     }
274 
275     public int getSocketTimeout() {
276         final Socket socket = this.socketHolder.get();
277         if (socket != null) {
278             try {
279                 return socket.getSoTimeout();
280             } catch (final SocketException ignore) {
281                 return -1;
282             }
283         } else {
284             return -1;
285         }
286     }
287 
288     public void shutdown() throws IOException {
289         final Socket socket = this.socketHolder.getAndSet(null);
290         if (socket != null) {
291             socket.close();
292         }
293     }
294 
295     public void close() throws IOException {
296         final Socket socket = this.socketHolder.getAndSet(null);
297         if (socket != null) {
298             try {
299                 this.inbuffer.clear();
300                 this.outbuffer.flush();
301                 try {
302                     try {
303                         socket.shutdownOutput();
304                     } catch (final IOException ignore) {
305                     }
306                     try {
307                         socket.shutdownInput();
308                     } catch (final IOException ignore) {
309                     }
310                 } catch (final UnsupportedOperationException ignore) {
311                     // if one isn't supported, the other one isn't either
312                 }
313             } finally {
314                 socket.close();
315             }
316         }
317     }
318 
319     private int fillInputBuffer(final int timeout) throws IOException {
320         final Socket socket = this.socketHolder.get();
321         final int oldtimeout = socket.getSoTimeout();
322         try {
323             socket.setSoTimeout(timeout);
324             return this.inbuffer.fillBuffer();
325         } finally {
326             socket.setSoTimeout(oldtimeout);
327         }
328     }
329 
330     protected boolean awaitInput(final int timeout) throws IOException {
331         if (this.inbuffer.hasBufferedData()) {
332             return true;
333         }
334         fillInputBuffer(timeout);
335         return this.inbuffer.hasBufferedData();
336     }
337 
338     public boolean isStale() {
339         if (!isOpen()) {
340             return true;
341         }
342         try {
343             final int bytesRead = fillInputBuffer(1);
344             return bytesRead < 0;
345         } catch (final SocketTimeoutException ex) {
346             return false;
347         } catch (final IOException ex) {
348             return true;
349         }
350     }
351 
352     protected void incrementRequestCount() {
353         this.connMetrics.incrementRequestCount();
354     }
355 
356     protected void incrementResponseCount() {
357         this.connMetrics.incrementResponseCount();
358     }
359 
360     public HttpConnectionMetrics getMetrics() {
361         return this.connMetrics;
362     }
363 
364     @Override
365     public String toString() {
366         final Socket socket = this.socketHolder.get();
367         if (socket != null) {
368             final StringBuilder buffer = new StringBuilder();
369             final SocketAddress remoteAddress = socket.getRemoteSocketAddress();
370             final SocketAddress localAddress = socket.getLocalSocketAddress();
371             if (remoteAddress != null && localAddress != null) {
372                 NetUtils.formatAddress(buffer, localAddress);
373                 buffer.append("<->");
374                 NetUtils.formatAddress(buffer, remoteAddress);
375             }
376             return buffer.toString();
377         } else {
378             return "[Not bound]";
379         }
380     }
381 
382 }