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