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 MessageConstraints messageConstraints;
83      private final HttpConnectionMetricsImpl connMetrics;
84      private final ContentLengthStrategy incomingContentStrategy;
85      private final ContentLengthStrategy outgoingContentStrategy;
86      private final AtomicReference<Socket> socketHolder;
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} 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} simple type cast will be used for char to byte conversion.
97       * @param messageConstraints Message constraints. If {@code null}
98       *   {@link MessageConstraints#DEFAULT} will be used.
99       * @param incomingContentStrategy incoming content length strategy. If {@code null}
100      *   {@link LaxContentLengthStrategy#INSTANCE} will be used.
101      * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
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 messageConstraints,
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                 messageConstraints != null ? messageConstraints : MessageConstraints.DEFAULT, chardecoder);
118         this.outbuffer = new SessionOutputBufferImpl(outTransportMetrics, buffersize, fragmentSizeHint,
119                 charencoder);
120         this.messageConstraints = messageConstraints;
121         this.connMetrics = new HttpConnectionMetricsImpl(inTransportMetrics, outTransportMetrics);
122         this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy :
123             LaxContentLengthStrategy.INSTANCE;
124         this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy :
125             StrictContentLengthStrategy.INSTANCE;
126         this.socketHolder = new AtomicReference<Socket>();
127     }
128 
129     protected void ensureOpen() throws IOException {
130         final Socket socket = this.socketHolder.get();
131         Asserts.check(socket != null, "Connection is not open");
132         if (!this.inbuffer.isBound()) {
133             this.inbuffer.bind(getSocketInputStream(socket));
134         }
135         if (!this.outbuffer.isBound()) {
136             this.outbuffer.bind(getSocketOutputStream(socket));
137         }
138     }
139 
140     protected InputStream getSocketInputStream(final Socket socket) throws IOException {
141         return socket.getInputStream();
142     }
143 
144     protected OutputStream getSocketOutputStream(final Socket socket) throws IOException {
145         return socket.getOutputStream();
146     }
147 
148     /**
149      * Binds this connection to the given {@link Socket}. This socket will be
150      * used by the connection to send and receive data.
151      * <p>
152      * After this method's execution the connection status will be reported
153      * as open and the {@link #isOpen()} will return {@code true}.
154      *
155      * @param socket the socket.
156      * @throws IOException in case of an I/O error.
157      */
158     protected void bind(final Socket socket) throws IOException {
159         Args.notNull(socket, "Socket");
160         this.socketHolder.set(socket);
161         this.inbuffer.bind(null);
162         this.outbuffer.bind(null);
163     }
164 
165     protected SessionInputBuffer getSessionInputBuffer() {
166         return this.inbuffer;
167     }
168 
169     protected SessionOutputBuffer getSessionOutputBuffer() {
170         return this.outbuffer;
171     }
172 
173     protected void doFlush() throws IOException {
174         this.outbuffer.flush();
175     }
176 
177     @Override
178     public boolean isOpen() {
179         return this.socketHolder.get() != null;
180     }
181 
182     protected Socket getSocket() {
183         return this.socketHolder.get();
184     }
185 
186     protected OutputStream createOutputStream(
187             final long len,
188             final SessionOutputBuffer outbuffer) {
189         if (len == ContentLengthStrategy.CHUNKED) {
190             return new ChunkedOutputStream(2048, outbuffer);
191         } else if (len == ContentLengthStrategy.IDENTITY) {
192             return new IdentityOutputStream(outbuffer);
193         } else {
194             return new ContentLengthOutputStream(outbuffer, len);
195         }
196     }
197 
198     protected OutputStream prepareOutput(final HttpMessage message) throws HttpException {
199         final long len = this.outgoingContentStrategy.determineLength(message);
200         return createOutputStream(len, this.outbuffer);
201     }
202 
203     protected InputStream createInputStream(
204             final long len,
205             final SessionInputBuffer inbuffer) {
206         if (len == ContentLengthStrategy.CHUNKED) {
207             return new ChunkedInputStream(inbuffer, this.messageConstraints);
208         } else if (len == ContentLengthStrategy.IDENTITY) {
209             return new IdentityInputStream(inbuffer);
210         } else {
211             return new ContentLengthInputStream(inbuffer, len);
212         }
213     }
214 
215     protected HttpEntity prepareInput(final HttpMessage message) throws HttpException {
216         final BasicHttpEntity entity = new BasicHttpEntity();
217 
218         final long len = this.incomingContentStrategy.determineLength(message);
219         final InputStream instream = createInputStream(len, this.inbuffer);
220         if (len == ContentLengthStrategy.CHUNKED) {
221             entity.setChunked(true);
222             entity.setContentLength(-1);
223             entity.setContent(instream);
224         } else if (len == ContentLengthStrategy.IDENTITY) {
225             entity.setChunked(false);
226             entity.setContentLength(-1);
227             entity.setContent(instream);
228         } else {
229             entity.setChunked(false);
230             entity.setContentLength(len);
231             entity.setContent(instream);
232         }
233 
234         final Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE);
235         if (contentTypeHeader != null) {
236             entity.setContentType(contentTypeHeader);
237         }
238         final Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING);
239         if (contentEncodingHeader != null) {
240             entity.setContentEncoding(contentEncodingHeader);
241         }
242         return entity;
243     }
244 
245     @Override
246     public InetAddress getLocalAddress() {
247         final Socket socket = this.socketHolder.get();
248         return socket != null ? socket.getLocalAddress() : null;
249     }
250 
251     @Override
252     public int getLocalPort() {
253         final Socket socket = this.socketHolder.get();
254         return socket != null ? socket.getLocalPort() : -1;
255     }
256 
257     @Override
258     public InetAddress getRemoteAddress() {
259         final Socket socket = this.socketHolder.get();
260         return socket != null ? socket.getInetAddress() : null;
261     }
262 
263     @Override
264     public int getRemotePort() {
265         final Socket socket = this.socketHolder.get();
266         return socket != null ? socket.getPort() : -1;
267     }
268 
269     @Override
270     public void setSocketTimeout(final int timeout) {
271         final Socket socket = this.socketHolder.get();
272         if (socket != null) {
273             try {
274                 socket.setSoTimeout(timeout);
275             } catch (final SocketException ignore) {
276                 // It is not quite clear from the Sun's documentation if there are any
277                 // other legitimate cases for a socket exception to be thrown when setting
278                 // SO_TIMEOUT besides the socket being already closed
279             }
280         }
281     }
282 
283     @Override
284     public int getSocketTimeout() {
285         final Socket socket = this.socketHolder.get();
286         if (socket != null) {
287             try {
288                 return socket.getSoTimeout();
289             } catch (final SocketException ignore) {
290                 return -1;
291             }
292         } else {
293             return -1;
294         }
295     }
296 
297     @Override
298     public void shutdown() throws IOException {
299         final Socket socket = this.socketHolder.getAndSet(null);
300         if (socket != null) {
301             // force abortive close (RST)
302             socket.setSoLinger(true, 0);
303             socket.close();
304         }
305     }
306 
307     @Override
308     public void close() throws IOException {
309         final Socket socket = this.socketHolder.getAndSet(null);
310         if (socket != null) {
311             try {
312                 this.inbuffer.clear();
313                 this.outbuffer.flush();
314                 try {
315                     try {
316                         socket.shutdownOutput();
317                     } catch (final IOException ignore) {
318                     }
319                     try {
320                         socket.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                 socket.close();
328             }
329         }
330     }
331 
332     private int fillInputBuffer(final int timeout) throws IOException {
333         final Socket socket = this.socketHolder.get();
334         final int oldtimeout = socket.getSoTimeout();
335         try {
336             socket.setSoTimeout(timeout);
337             return this.inbuffer.fillBuffer();
338         } finally {
339             socket.setSoTimeout(oldtimeout);
340         }
341     }
342 
343     protected boolean awaitInput(final int timeout) throws IOException {
344         if (this.inbuffer.hasBufferedData()) {
345             return true;
346         }
347         fillInputBuffer(timeout);
348         return this.inbuffer.hasBufferedData();
349     }
350 
351     @Override
352     public boolean isStale() {
353         if (!isOpen()) {
354             return true;
355         }
356         try {
357             final int bytesRead = fillInputBuffer(1);
358             return bytesRead < 0;
359         } catch (final SocketTimeoutException ex) {
360             return false;
361         } catch (final IOException ex) {
362             return true;
363         }
364     }
365 
366     protected void incrementRequestCount() {
367         this.connMetrics.incrementRequestCount();
368     }
369 
370     protected void incrementResponseCount() {
371         this.connMetrics.incrementResponseCount();
372     }
373 
374     @Override
375     public HttpConnectionMetrics getMetrics() {
376         return this.connMetrics;
377     }
378 
379     @Override
380     public String toString() {
381         final Socket socket = this.socketHolder.get();
382         if (socket != null) {
383             final StringBuilder buffer = new StringBuilder();
384             final SocketAddress remoteAddress = socket.getRemoteSocketAddress();
385             final SocketAddress localAddress = socket.getLocalSocketAddress();
386             if (remoteAddress != null && localAddress != null) {
387                 NetUtils.formatAddress(buffer, localAddress);
388                 buffer.append("<->");
389                 NetUtils.formatAddress(buffer, remoteAddress);
390             }
391             return buffer.toString();
392         } else {
393             return "[Not bound]";
394         }
395     }
396 
397 }