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.EmptyInputStream;
60  import org.apache.http.impl.io.HttpTransportMetricsImpl;
61  import org.apache.http.impl.io.IdentityInputStream;
62  import org.apache.http.impl.io.IdentityOutputStream;
63  import org.apache.http.impl.io.SessionInputBufferImpl;
64  import org.apache.http.impl.io.SessionOutputBufferImpl;
65  import org.apache.http.io.SessionInputBuffer;
66  import org.apache.http.io.SessionOutputBuffer;
67  import org.apache.http.protocol.HTTP;
68  import org.apache.http.util.Args;
69  import org.apache.http.util.Asserts;
70  import org.apache.http.util.NetUtils;
71  
72  /**
73   * This class serves as a base for all {@link HttpConnection} implementations and provides
74   * functionality common to both client and server HTTP connections.
75   *
76   * @since 4.0
77   */
78  @NotThreadSafe
79  public class BHttpConnectionBase implements HttpConnection, HttpInetConnection {
80  
81      private final SessionInputBufferImpl inbuffer;
82      private final SessionOutputBufferImpl outbuffer;
83      private final MessageConstraints messageConstraints;
84      private final HttpConnectionMetricsImpl connMetrics;
85      private final ContentLengthStrategy incomingContentStrategy;
86      private final ContentLengthStrategy outgoingContentStrategy;
87      private final AtomicReference<Socket> socketHolder;
88  
89      /**
90       * Creates new instance of BHttpConnectionBase.
91       *
92       * @param buffersize buffer size. Must be a positive number.
93       * @param fragmentSizeHint fragment size hint.
94       * @param chardecoder decoder to be used for decoding HTTP protocol elements.
95       *   If {@code null} simple type cast will be used for byte to char conversion.
96       * @param charencoder encoder to be used for encoding HTTP protocol elements.
97       *   If {@code null} simple type cast will be used for char to byte conversion.
98       * @param messageConstraints Message constraints. If {@code null}
99       *   {@link MessageConstraints#DEFAULT} will be used.
100      * @param incomingContentStrategy incoming content length strategy. If {@code null}
101      *   {@link LaxContentLengthStrategy#INSTANCE} will be used.
102      * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
103      *   {@link StrictContentLengthStrategy#INSTANCE} will be used.
104      */
105     protected BHttpConnectionBase(
106             final int buffersize,
107             final int fragmentSizeHint,
108             final CharsetDecoder chardecoder,
109             final CharsetEncoder charencoder,
110             final MessageConstraints messageConstraints,
111             final ContentLengthStrategy incomingContentStrategy,
112             final ContentLengthStrategy outgoingContentStrategy) {
113         super();
114         Args.positive(buffersize, "Buffer size");
115         final HttpTransportMetricsImpl inTransportMetrics = new HttpTransportMetricsImpl();
116         final HttpTransportMetricsImpl outTransportMetrics = new HttpTransportMetricsImpl();
117         this.inbuffer = new SessionInputBufferImpl(inTransportMetrics, buffersize, -1,
118                 messageConstraints != null ? messageConstraints : MessageConstraints.DEFAULT, chardecoder);
119         this.outbuffer = new SessionOutputBufferImpl(outTransportMetrics, buffersize, fragmentSizeHint,
120                 charencoder);
121         this.messageConstraints = messageConstraints;
122         this.connMetrics = new HttpConnectionMetricsImpl(inTransportMetrics, outTransportMetrics);
123         this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy :
124             LaxContentLengthStrategy.INSTANCE;
125         this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy :
126             StrictContentLengthStrategy.INSTANCE;
127         this.socketHolder = new AtomicReference<Socket>();
128     }
129 
130     protected void ensureOpen() throws IOException {
131         final Socket socket = this.socketHolder.get();
132         Asserts.check(socket != null, "Connection is not open");
133         if (!this.inbuffer.isBound()) {
134             this.inbuffer.bind(getSocketInputStream(socket));
135         }
136         if (!this.outbuffer.isBound()) {
137             this.outbuffer.bind(getSocketOutputStream(socket));
138         }
139     }
140 
141     protected InputStream getSocketInputStream(final Socket socket) throws IOException {
142         return socket.getInputStream();
143     }
144 
145     protected OutputStream getSocketOutputStream(final Socket socket) throws IOException {
146         return socket.getOutputStream();
147     }
148 
149     /**
150      * Binds this connection to the given {@link Socket}. This socket will be
151      * used by the connection to send and receive data.
152      * <p>
153      * After this method's execution the connection status will be reported
154      * as open and the {@link #isOpen()} will return {@code true}.
155      *
156      * @param socket the socket.
157      * @throws IOException in case of an I/O error.
158      */
159     protected void bind(final Socket socket) throws IOException {
160         Args.notNull(socket, "Socket");
161         this.socketHolder.set(socket);
162         this.inbuffer.bind(null);
163         this.outbuffer.bind(null);
164     }
165 
166     protected SessionInputBuffer getSessionInputBuffer() {
167         return this.inbuffer;
168     }
169 
170     protected SessionOutputBuffer getSessionOutputBuffer() {
171         return this.outbuffer;
172     }
173 
174     protected void doFlush() throws IOException {
175         this.outbuffer.flush();
176     }
177 
178     @Override
179     public boolean isOpen() {
180         return this.socketHolder.get() != null;
181     }
182 
183     protected Socket getSocket() {
184         return this.socketHolder.get();
185     }
186 
187     protected OutputStream createOutputStream(
188             final long len,
189             final SessionOutputBuffer outbuffer) {
190         if (len == ContentLengthStrategy.CHUNKED) {
191             return new ChunkedOutputStream(2048, outbuffer);
192         } else if (len == ContentLengthStrategy.IDENTITY) {
193             return new IdentityOutputStream(outbuffer);
194         } else {
195             return new ContentLengthOutputStream(outbuffer, len);
196         }
197     }
198 
199     protected OutputStream prepareOutput(final HttpMessage message) throws HttpException {
200         final long len = this.outgoingContentStrategy.determineLength(message);
201         return createOutputStream(len, this.outbuffer);
202     }
203 
204     protected InputStream createInputStream(
205             final long len,
206             final SessionInputBuffer inbuffer) {
207         if (len == ContentLengthStrategy.CHUNKED) {
208             return new ChunkedInputStream(inbuffer, this.messageConstraints);
209         } else if (len == ContentLengthStrategy.IDENTITY) {
210             return new IdentityInputStream(inbuffer);
211         } else if (len == 0L) {
212             return EmptyInputStream.INSTANCE;
213         } else {
214             return new ContentLengthInputStream(inbuffer, len);
215         }
216     }
217 
218     protected HttpEntity prepareInput(final HttpMessage message) throws HttpException {
219         final BasicHttpEntity entity = new BasicHttpEntity();
220 
221         final long len = this.incomingContentStrategy.determineLength(message);
222         final InputStream instream = createInputStream(len, this.inbuffer);
223         if (len == ContentLengthStrategy.CHUNKED) {
224             entity.setChunked(true);
225             entity.setContentLength(-1);
226             entity.setContent(instream);
227         } else if (len == ContentLengthStrategy.IDENTITY) {
228             entity.setChunked(false);
229             entity.setContentLength(-1);
230             entity.setContent(instream);
231         } else {
232             entity.setChunked(false);
233             entity.setContentLength(len);
234             entity.setContent(instream);
235         }
236 
237         final Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE);
238         if (contentTypeHeader != null) {
239             entity.setContentType(contentTypeHeader);
240         }
241         final Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING);
242         if (contentEncodingHeader != null) {
243             entity.setContentEncoding(contentEncodingHeader);
244         }
245         return entity;
246     }
247 
248     @Override
249     public InetAddress getLocalAddress() {
250         final Socket socket = this.socketHolder.get();
251         return socket != null ? socket.getLocalAddress() : null;
252     }
253 
254     @Override
255     public int getLocalPort() {
256         final Socket socket = this.socketHolder.get();
257         return socket != null ? socket.getLocalPort() : -1;
258     }
259 
260     @Override
261     public InetAddress getRemoteAddress() {
262         final Socket socket = this.socketHolder.get();
263         return socket != null ? socket.getInetAddress() : null;
264     }
265 
266     @Override
267     public int getRemotePort() {
268         final Socket socket = this.socketHolder.get();
269         return socket != null ? socket.getPort() : -1;
270     }
271 
272     @Override
273     public void setSocketTimeout(final int timeout) {
274         final Socket socket = this.socketHolder.get();
275         if (socket != null) {
276             try {
277                 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     @Override
287     public int getSocketTimeout() {
288         final Socket socket = this.socketHolder.get();
289         if (socket != null) {
290             try {
291                 return socket.getSoTimeout();
292             } catch (final SocketException ignore) {
293                 return -1;
294             }
295         } else {
296             return -1;
297         }
298     }
299 
300     @Override
301     public void shutdown() throws IOException {
302         final Socket socket = this.socketHolder.getAndSet(null);
303         if (socket != null) {
304             // force abortive close (RST)
305             socket.setSoLinger(true, 0);
306             socket.close();
307         }
308     }
309 
310     @Override
311     public void close() throws IOException {
312         final Socket socket = this.socketHolder.getAndSet(null);
313         if (socket != null) {
314             try {
315                 this.inbuffer.clear();
316                 this.outbuffer.flush();
317                 try {
318                     try {
319                         socket.shutdownOutput();
320                     } catch (final IOException ignore) {
321                     }
322                     try {
323                         socket.shutdownInput();
324                     } catch (final IOException ignore) {
325                     }
326                 } catch (final UnsupportedOperationException ignore) {
327                     // if one isn't supported, the other one isn't either
328                 }
329             } finally {
330                 socket.close();
331             }
332         }
333     }
334 
335     private int fillInputBuffer(final int timeout) throws IOException {
336         final Socket socket = this.socketHolder.get();
337         final int oldtimeout = socket.getSoTimeout();
338         try {
339             socket.setSoTimeout(timeout);
340             return this.inbuffer.fillBuffer();
341         } finally {
342             socket.setSoTimeout(oldtimeout);
343         }
344     }
345 
346     protected boolean awaitInput(final int timeout) throws IOException {
347         if (this.inbuffer.hasBufferedData()) {
348             return true;
349         }
350         fillInputBuffer(timeout);
351         return this.inbuffer.hasBufferedData();
352     }
353 
354     @Override
355     public boolean isStale() {
356         if (!isOpen()) {
357             return true;
358         }
359         try {
360             final int bytesRead = fillInputBuffer(1);
361             return bytesRead < 0;
362         } catch (final SocketTimeoutException ex) {
363             return false;
364         } catch (final IOException ex) {
365             return true;
366         }
367     }
368 
369     protected void incrementRequestCount() {
370         this.connMetrics.incrementRequestCount();
371     }
372 
373     protected void incrementResponseCount() {
374         this.connMetrics.incrementResponseCount();
375     }
376 
377     @Override
378     public HttpConnectionMetrics getMetrics() {
379         return this.connMetrics;
380     }
381 
382     @Override
383     public String toString() {
384         final Socket socket = this.socketHolder.get();
385         if (socket != null) {
386             final StringBuilder buffer = new StringBuilder();
387             final SocketAddress remoteAddress = socket.getRemoteSocketAddress();
388             final SocketAddress localAddress = socket.getLocalSocketAddress();
389             if (remoteAddress != null && localAddress != null) {
390                 NetUtils.formatAddress(buffer, localAddress);
391                 buffer.append("<->");
392                 NetUtils.formatAddress(buffer, remoteAddress);
393             }
394             return buffer.toString();
395         } else {
396             return "[Not bound]";
397         }
398     }
399 
400 }