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