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