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