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.hc.core5.http.impl.io;
29  
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.net.Socket;
34  import java.net.SocketAddress;
35  import java.net.SocketException;
36  import java.net.SocketTimeoutException;
37  import java.nio.charset.CharsetDecoder;
38  import java.nio.charset.CharsetEncoder;
39  import java.util.List;
40  import java.util.concurrent.atomic.AtomicReference;
41  
42  import javax.net.ssl.SSLSession;
43  import javax.net.ssl.SSLSocket;
44  
45  import org.apache.hc.core5.function.Supplier;
46  import org.apache.hc.core5.http.ConnectionClosedException;
47  import org.apache.hc.core5.http.ContentLengthStrategy;
48  import org.apache.hc.core5.http.EndpointDetails;
49  import org.apache.hc.core5.http.Header;
50  import org.apache.hc.core5.http.HttpEntity;
51  import org.apache.hc.core5.http.HttpHeaders;
52  import org.apache.hc.core5.http.HttpMessage;
53  import org.apache.hc.core5.http.ProtocolVersion;
54  import org.apache.hc.core5.http.config.Http1Config;
55  import org.apache.hc.core5.http.impl.BasicEndpointDetails;
56  import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics;
57  import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
58  import org.apache.hc.core5.http.io.BHttpConnection;
59  import org.apache.hc.core5.http.io.SessionInputBuffer;
60  import org.apache.hc.core5.http.io.SessionOutputBuffer;
61  import org.apache.hc.core5.http.io.entity.EmptyInputStream;
62  import org.apache.hc.core5.io.CloseMode;
63  import org.apache.hc.core5.io.Closer;
64  import org.apache.hc.core5.net.InetAddressUtils;
65  import org.apache.hc.core5.util.Args;
66  import org.apache.hc.core5.util.Timeout;
67  
68  class BHttpConnectionBase implements BHttpConnection {
69  
70      final Http1Config http1Config;
71      final SessionInputBufferImpl inBuffer;
72      final SessionOutputBufferImpl outbuffer;
73      final BasicHttpConnectionMetrics connMetrics;
74      final AtomicReference<SocketHolder> socketHolderRef;
75      // Lazily initialized chunked request buffer provided to ChunkedOutputStream.
76      private byte[] chunkedRequestBuffer;
77  
78      volatile ProtocolVersion version;
79      volatile EndpointDetails endpointDetails;
80  
81      BHttpConnectionBase(
82              final Http1Config http1Config,
83              final CharsetDecoder charDecoder,
84              final CharsetEncoder charEncoder) {
85          this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT;
86          final BasicHttpTransportMetricstrics.html#BasicHttpTransportMetrics">BasicHttpTransportMetrics inTransportMetrics = new BasicHttpTransportMetrics();
87          final BasicHttpTransportMetricsrics.html#BasicHttpTransportMetrics">BasicHttpTransportMetrics outTransportMetrics = new BasicHttpTransportMetrics();
88          this.inBuffer = new SessionInputBufferImpl(inTransportMetrics,
89                  this.http1Config.getBufferSize(), -1,
90                  this.http1Config.getMaxLineLength(), charDecoder);
91          this.outbuffer = new SessionOutputBufferImpl(outTransportMetrics,
92                  this.http1Config.getBufferSize(),
93                  this.http1Config.getChunkSizeHint(), charEncoder);
94          this.connMetrics = new BasicHttpConnectionMetrics(inTransportMetrics, outTransportMetrics);
95          this.socketHolderRef = new AtomicReference<>();
96      }
97  
98      protected SocketHolder ensureOpen() throws IOException {
99          final SocketHolder socketHolder = this.socketHolderRef.get();
100         if (socketHolder == null) {
101             throw new ConnectionClosedException();
102         }
103         return socketHolder;
104     }
105 
106     /**
107      * Binds this connection to the given {@link Socket}. This socket will be
108      * used by the connection to send and receive data.
109      * <p>
110      * After this method's execution the connection status will be reported
111      * as open and the {@link #isOpen()} will return {@code true}.
112      *
113      * @param socket the socket.
114      * @throws IOException in case of an I/O error.
115      */
116     protected void bind(final Socket socket) throws IOException {
117         Args.notNull(socket, "Socket");
118         bind(new SocketHolder(socket));
119     }
120 
121     protected void bind(final SocketHolder socketHolder) throws IOException {
122         Args.notNull(socketHolder, "Socket holder");
123         this.socketHolderRef.set(socketHolder);
124         this.endpointDetails = null;
125     }
126 
127     @Override
128     public boolean isOpen() {
129         return this.socketHolderRef.get() != null;
130     }
131 
132     /**
133      * @since 5.0
134      */
135     @Override
136     public ProtocolVersion getProtocolVersion() {
137         return this.version;
138     }
139 
140     protected SocketHolder getSocketHolder() {
141         return this.socketHolderRef.get();
142     }
143 
144     protected OutputStream createContentOutputStream(
145             final long len,
146             final SessionOutputBuffer buffer,
147             final OutputStream outputStream,
148             final Supplier<List<? extends Header>> trailers) {
149         if (len >= 0) {
150             return new ContentLengthOutputStream(buffer, outputStream, len);
151         } else if (len == ContentLengthStrategy.CHUNKED) {
152             return new ChunkedOutputStream(buffer, outputStream, getChunkedRequestBuffer(), trailers);
153         } else {
154             return new IdentityOutputStream(buffer, outputStream);
155         }
156     }
157 
158     private byte[] getChunkedRequestBuffer() {
159         if (chunkedRequestBuffer == null) {
160             final int chunkSizeHint = this.http1Config.getChunkSizeHint();
161             chunkedRequestBuffer = new byte[chunkSizeHint > 0 ? chunkSizeHint : 8192];
162         }
163         return chunkedRequestBuffer;
164     }
165 
166     protected InputStream createContentInputStream(
167             final long len,
168             final SessionInputBuffer buffer,
169             final InputStream inputStream) {
170         if (len > 0) {
171             return new ContentLengthInputStream(buffer, inputStream, len);
172         } else if (len == 0) {
173             return EmptyInputStream.INSTANCE;
174         } else if (len == ContentLengthStrategy.CHUNKED) {
175             return new ChunkedInputStream(buffer, inputStream, this.http1Config);
176         } else {
177             return new IdentityInputStream(buffer, inputStream);
178         }
179     }
180 
181     HttpEntity createIncomingEntity(
182             final HttpMessage message,
183             final SessionInputBuffer inBuffer,
184             final InputStream inputStream,
185             final long len) {
186         return new IncomingHttpEntity(
187                 createContentInputStream(len, inBuffer, inputStream),
188                 len >= 0 ? len : -1, len == ContentLengthStrategy.CHUNKED,
189                 message.getFirstHeader(HttpHeaders.CONTENT_TYPE),
190                 message.getFirstHeader(HttpHeaders.CONTENT_ENCODING));
191     }
192 
193     @Override
194     public SocketAddress getRemoteAddress() {
195         final SocketHolder socketHolder = this.socketHolderRef.get();
196         return socketHolder != null ? socketHolder.getSocket().getRemoteSocketAddress() : null;
197     }
198 
199     @Override
200     public SocketAddress getLocalAddress() {
201         final SocketHolder socketHolder = this.socketHolderRef.get();
202         return socketHolder != null ? socketHolder.getSocket().getLocalSocketAddress() : null;
203     }
204 
205     @Override
206     public void setSocketTimeout(final Timeout timeout) {
207         final SocketHolder socketHolder = this.socketHolderRef.get();
208         if (socketHolder != null) {
209             try {
210                 socketHolder.getSocket().setSoTimeout(Timeout.defaultsToDisabled(timeout).toMillisecondsIntBound());
211             } catch (final SocketException ignore) {
212                 // It is not quite clear from the Sun's documentation if there are any
213                 // other legitimate cases for a socket exception to be thrown when setting
214                 // SO_TIMEOUT besides the socket being already closed
215             }
216         }
217     }
218 
219     @Override
220     public Timeout getSocketTimeout() {
221         final SocketHolder socketHolder = this.socketHolderRef.get();
222         if (socketHolder != null) {
223             try {
224                 return Timeout.ofMilliseconds(socketHolder.getSocket().getSoTimeout());
225             } catch (final SocketException ignore) {
226             }
227         }
228         return Timeout.DISABLED;
229     }
230 
231     @Override
232     public void close(final CloseMode closeMode) {
233         final SocketHolder socketHolder = this.socketHolderRef.getAndSet(null);
234         if (socketHolder != null) {
235             final Socket socket = socketHolder.getSocket();
236             try {
237                 if (closeMode == CloseMode.IMMEDIATE) {
238                     // force abortive close (RST)
239                     socket.setSoLinger(true, 0);
240                 }
241             } catch (final IOException ignore) {
242             } finally {
243                 Closer.closeQuietly(socket);
244             }
245         }
246     }
247 
248     @Override
249     public void close() throws IOException {
250         final SocketHolder socketHolder = this.socketHolderRef.getAndSet(null);
251         if (socketHolder != null) {
252             try (final Socket socket = socketHolder.getSocket()) {
253                 this.inBuffer.clear();
254                 this.outbuffer.flush(socketHolder.getOutputStream());
255             }
256         }
257     }
258 
259     private int fillInputBuffer(final Timeout timeout) throws IOException {
260         final SocketHolder socketHolder = ensureOpen();
261         final Socket socket = socketHolder.getSocket();
262         final int oldtimeout = socket.getSoTimeout();
263         try {
264             socket.setSoTimeout(timeout.toMillisecondsIntBound());
265             return this.inBuffer.fillBuffer(socketHolder.getInputStream());
266         } finally {
267             socket.setSoTimeout(oldtimeout);
268         }
269     }
270 
271     protected boolean awaitInput(final Timeout timeout) throws IOException {
272         if (this.inBuffer.hasBufferedData()) {
273             return true;
274         }
275         fillInputBuffer(timeout);
276         return this.inBuffer.hasBufferedData();
277     }
278 
279     @Override
280     public boolean isDataAvailable(final Timeout timeout) throws IOException {
281         ensureOpen();
282         try {
283             return awaitInput(timeout);
284         } catch (final SocketTimeoutException ex) {
285             return false;
286         }
287     }
288 
289     @Override
290     public boolean isStale() throws IOException {
291         if (!isOpen()) {
292             return true;
293         }
294         try {
295             final int bytesRead = fillInputBuffer(Timeout.ofMilliseconds(1));
296             return bytesRead < 0;
297         } catch (final SocketTimeoutException ex) {
298             return false;
299         } catch (final SocketException ex) {
300             return true;
301         }
302     }
303 
304     @Override
305     public void flush() throws IOException {
306         final SocketHolder socketHolder = ensureOpen();
307         this.outbuffer.flush(socketHolder.getOutputStream());
308     }
309 
310     protected void incrementRequestCount() {
311         this.connMetrics.incrementRequestCount();
312     }
313 
314     protected void incrementResponseCount() {
315         this.connMetrics.incrementResponseCount();
316     }
317 
318     @Override
319     public SSLSession getSSLSession() {
320         final SocketHolder socketHolder = this.socketHolderRef.get();
321         if (socketHolder != null) {
322             final Socket socket = socketHolder.getSocket();
323             return socket instanceof SSLSocket ? ((SSLSocket) socket).getSession() : null;
324         }
325         return null;
326     }
327 
328     @Override
329     public EndpointDetails getEndpointDetails() {
330         if (endpointDetails == null) {
331             final SocketHolder socketHolder = this.socketHolderRef.get();
332             if (socketHolder != null) {
333                 @SuppressWarnings("resource")
334                 final Socket socket = socketHolder.getSocket();
335                 Timeout socketTimeout;
336                 try {
337                     socketTimeout = Timeout.ofMilliseconds(socket.getSoTimeout());
338                 } catch (final SocketException e) {
339                     socketTimeout = Timeout.DISABLED;
340                 }
341                 endpointDetails = new BasicEndpointDetails(
342                         socket.getRemoteSocketAddress(),
343                         socket.getLocalSocketAddress(),
344                         this.connMetrics,
345                         socketTimeout);
346             }
347         }
348         return endpointDetails;
349     }
350 
351     @Override
352     public String toString() {
353         final SocketHolder socketHolder = this.socketHolderRef.get();
354         if (socketHolder != null) {
355             final Socket socket = socketHolder.getSocket();
356             final StringBuilder buffer = new StringBuilder();
357             final SocketAddress remoteAddress = socket.getRemoteSocketAddress();
358             final SocketAddress localAddress = socket.getLocalSocketAddress();
359             if (remoteAddress != null && localAddress != null) {
360                 InetAddressUtils.formatAddress(buffer, localAddress);
361                 buffer.append("<->");
362                 InetAddressUtils.formatAddress(buffer, remoteAddress);
363             }
364             return buffer.toString();
365         }
366         return "[Not bound]";
367     }
368 
369 }