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