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.ConnectionClosedException;
43  import org.apache.http.Header;
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.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.EmptyInputStream;
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.NetUtils;
69  
70  /**
71   * This class serves as a base for all {@link org.apache.http.HttpConnection} implementations
72   * and provides functionality common to both client and server HTTP connections.
73   *
74   * @since 4.0
75   */
76  public class BHttpConnectionBase implements HttpInetConnection {
77  
78      private final SessionInputBufferImpl inBuffer;
79      private final SessionOutputBufferImpl outbuffer;
80      private final MessageConstraints messageConstraints;
81      private final HttpConnectionMetricsImpl connMetrics;
82      private final ContentLengthStrategy incomingContentStrategy;
83      private final ContentLengthStrategy outgoingContentStrategy;
84      private final AtomicReference<Socket> socketHolder;
85  
86      /**
87       * Creates new instance of BHttpConnectionBase.
88       *
89       * @param bufferSize buffer size. Must be a positive number.
90       * @param fragmentSizeHint fragment size hint.
91       * @param charDecoder decoder to be used for decoding HTTP protocol elements.
92       *   If {@code null} simple type cast will be used for byte to char conversion.
93       * @param charEncoder encoder to be used for encoding HTTP protocol elements.
94       *   If {@code null} simple type cast will be used for char to byte conversion.
95       * @param messageConstraints Message constraints. If {@code null}
96       *   {@link MessageConstraints#DEFAULT} will be used.
97       * @param incomingContentStrategy incoming content length strategy. If {@code null}
98       *   {@link LaxContentLengthStrategy#INSTANCE} will be used.
99       * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
100      *   {@link StrictContentLengthStrategy#INSTANCE} will be used.
101      */
102     protected BHttpConnectionBase(
103             final int bufferSize,
104             final int fragmentSizeHint,
105             final CharsetDecoder charDecoder,
106             final CharsetEncoder charEncoder,
107             final MessageConstraints messageConstraints,
108             final ContentLengthStrategy incomingContentStrategy,
109             final ContentLengthStrategy outgoingContentStrategy) {
110         super();
111         Args.positive(bufferSize, "Buffer size");
112         final HttpTransportMetricsImplTransportMetricsImpl">HttpTransportMetricsImpl inTransportMetrics = new HttpTransportMetricsImpl();
113         final HttpTransportMetricsImplransportMetricsImpl">HttpTransportMetricsImpl outTransportMetrics = new HttpTransportMetricsImpl();
114         this.inBuffer = new SessionInputBufferImpl(inTransportMetrics, bufferSize, -1,
115                 messageConstraints != null ? messageConstraints : MessageConstraints.DEFAULT, charDecoder);
116         this.outbuffer = new SessionOutputBufferImpl(outTransportMetrics, bufferSize, fragmentSizeHint,
117                 charEncoder);
118         this.messageConstraints = messageConstraints;
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         if (socket == null) {
130             throw new ConnectionClosedException();
131         }
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 if (len == 0L) {
211             return EmptyInputStream.INSTANCE;
212         } else {
213             return new ContentLengthInputStream(inBuffer, len);
214         }
215     }
216 
217     protected HttpEntity prepareInput(final HttpMessage message) throws HttpException {
218         final BasicHttpEntityy.html#BasicHttpEntity">BasicHttpEntity entity = new BasicHttpEntity();
219 
220         final long len = this.incomingContentStrategy.determineLength(message);
221         final InputStream inStream = createInputStream(len, this.inBuffer);
222         if (len == ContentLengthStrategy.CHUNKED) {
223             entity.setChunked(true);
224             entity.setContentLength(-1);
225             entity.setContent(inStream);
226         } else if (len == ContentLengthStrategy.IDENTITY) {
227             entity.setChunked(false);
228             entity.setContentLength(-1);
229             entity.setContent(inStream);
230         } else {
231             entity.setChunked(false);
232             entity.setContentLength(len);
233             entity.setContent(inStream);
234         }
235 
236         final Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE);
237         if (contentTypeHeader != null) {
238             entity.setContentType(contentTypeHeader);
239         }
240         final Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING);
241         if (contentEncodingHeader != null) {
242             entity.setContentEncoding(contentEncodingHeader);
243         }
244         return entity;
245     }
246 
247     @Override
248     public InetAddress getLocalAddress() {
249         final Socket socket = this.socketHolder.get();
250         return socket != null ? socket.getLocalAddress() : null;
251     }
252 
253     @Override
254     public int getLocalPort() {
255         final Socket socket = this.socketHolder.get();
256         return socket != null ? socket.getLocalPort() : -1;
257     }
258 
259     @Override
260     public InetAddress getRemoteAddress() {
261         final Socket socket = this.socketHolder.get();
262         return socket != null ? socket.getInetAddress() : null;
263     }
264 
265     @Override
266     public int getRemotePort() {
267         final Socket socket = this.socketHolder.get();
268         return socket != null ? socket.getPort() : -1;
269     }
270 
271     @Override
272     public void setSocketTimeout(final int timeout) {
273         final Socket socket = this.socketHolder.get();
274         if (socket != null) {
275             try {
276                 socket.setSoTimeout(timeout);
277             } catch (final SocketException ignore) {
278                 // It is not quite clear from the Sun's documentation if there are any
279                 // other legitimate cases for a socket exception to be thrown when setting
280                 // SO_TIMEOUT besides the socket being already closed
281             }
282         }
283     }
284 
285     @Override
286     public int getSocketTimeout() {
287         final Socket socket = this.socketHolder.get();
288         if (socket != null) {
289             try {
290                 return socket.getSoTimeout();
291             } catch (final SocketException ignore) {
292                 // ignore
293             }
294         }
295         return -1;
296     }
297 
298     @Override
299     public void shutdown() throws IOException {
300         final Socket socket = this.socketHolder.getAndSet(null);
301         if (socket != null) {
302             // force abortive close (RST)
303             try {
304                 socket.setSoLinger(true, 0);
305             } catch (final IOException ex) {
306             } finally {
307                 socket.close();
308             }
309         }
310     }
311 
312     @Override
313     public void close() throws IOException {
314         final Socket socket = this.socketHolder.getAndSet(null);
315         if (socket != null) {
316             try {
317                 this.inBuffer.clear();
318                 this.outbuffer.flush();
319                 try {
320                     try {
321                         socket.shutdownOutput();
322                     } catch (final IOException ignore) {
323                     }
324                     try {
325                         socket.shutdownInput();
326                     } catch (final IOException ignore) {
327                     }
328                 } catch (final UnsupportedOperationException ignore) {
329                     // if one isn't supported, the other one isn't either
330                 }
331             } finally {
332                 socket.close();
333             }
334         }
335     }
336 
337     private int fillInputBuffer(final int timeout) throws IOException {
338         final Socket socket = this.socketHolder.get();
339         final int oldtimeout = socket.getSoTimeout();
340         try {
341             socket.setSoTimeout(timeout);
342             return this.inBuffer.fillBuffer();
343         } finally {
344             socket.setSoTimeout(oldtimeout);
345         }
346     }
347 
348     protected boolean awaitInput(final int timeout) throws IOException {
349         if (this.inBuffer.hasBufferedData()) {
350             return true;
351         }
352         fillInputBuffer(timeout);
353         return this.inBuffer.hasBufferedData();
354     }
355 
356     @Override
357     public boolean isStale() {
358         if (!isOpen()) {
359             return true;
360         }
361         try {
362             final int bytesRead = fillInputBuffer(1);
363             return bytesRead < 0;
364         } catch (final SocketTimeoutException ex) {
365             return false;
366         } catch (final IOException ex) {
367             return true;
368         }
369     }
370 
371     protected void incrementRequestCount() {
372         this.connMetrics.incrementRequestCount();
373     }
374 
375     protected void incrementResponseCount() {
376         this.connMetrics.incrementResponseCount();
377     }
378 
379     @Override
380     public HttpConnectionMetrics getMetrics() {
381         return this.connMetrics;
382     }
383 
384     @Override
385     public String toString() {
386         final Socket socket = this.socketHolder.get();
387         if (socket != null) {
388             final StringBuilder buffer = new StringBuilder();
389             final SocketAddress remoteAddress = socket.getRemoteSocketAddress();
390             final SocketAddress localAddress = socket.getLocalSocketAddress();
391             if (remoteAddress != null && localAddress != null) {
392                 NetUtils.formatAddress(buffer, localAddress);
393                 buffer.append("<->");
394                 NetUtils.formatAddress(buffer, remoteAddress);
395             }
396             return buffer.toString();
397         }
398         return "[Not bound]";
399     }
400 
401 }