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.nio.charset.CharsetDecoder;
35  import java.nio.charset.CharsetEncoder;
36  import java.util.Iterator;
37  
38  import javax.net.ssl.SSLSocket;
39  
40  import org.apache.hc.core5.http.ClassicHttpRequest;
41  import org.apache.hc.core5.http.ClassicHttpResponse;
42  import org.apache.hc.core5.http.ConnectionClosedException;
43  import org.apache.hc.core5.http.ContentLengthStrategy;
44  import org.apache.hc.core5.http.HeaderElements;
45  import org.apache.hc.core5.http.HttpEntity;
46  import org.apache.hc.core5.http.HttpException;
47  import org.apache.hc.core5.http.HttpHeaders;
48  import org.apache.hc.core5.http.HttpStatus;
49  import org.apache.hc.core5.http.HttpVersion;
50  import org.apache.hc.core5.http.LengthRequiredException;
51  import org.apache.hc.core5.http.NoHttpResponseException;
52  import org.apache.hc.core5.http.ProtocolException;
53  import org.apache.hc.core5.http.ProtocolVersion;
54  import org.apache.hc.core5.http.UnsupportedHttpVersionException;
55  import org.apache.hc.core5.http.config.Http1Config;
56  import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
57  import org.apache.hc.core5.http.io.HttpClientConnection;
58  import org.apache.hc.core5.http.io.HttpMessageParser;
59  import org.apache.hc.core5.http.io.HttpMessageParserFactory;
60  import org.apache.hc.core5.http.io.HttpMessageWriter;
61  import org.apache.hc.core5.http.io.HttpMessageWriterFactory;
62  import org.apache.hc.core5.http.io.ResponseOutOfOrderStrategy;
63  import org.apache.hc.core5.http.message.MessageSupport;
64  import org.apache.hc.core5.util.Args;
65  
66  /**
67   * Default implementation of {@link HttpClientConnection}.
68   *
69   * @since 4.3
70   */
71  public class DefaultBHttpClientConnection extends BHttpConnectionBase
72                                                     implements HttpClientConnection {
73  
74      private final HttpMessageParser<ClassicHttpResponse> responseParser;
75      private final HttpMessageWriter<ClassicHttpRequest> requestWriter;
76      private final ContentLengthStrategy incomingContentStrategy;
77      private final ContentLengthStrategy outgoingContentStrategy;
78      private final ResponseOutOfOrderStrategy responseOutOfOrderStrategy;
79      private volatile boolean consistent;
80  
81      /**
82       * Creates new instance of DefaultBHttpClientConnection.
83       *
84       * @param http1Config Message http1Config. If {@code null}
85       *   {@link Http1Config#DEFAULT} will be used.
86       * @param charDecoder decoder to be used for decoding HTTP protocol elements.
87       *   If {@code null} simple type cast will be used for byte to char conversion.
88       * @param charEncoder encoder to be used for encoding HTTP protocol elements.
89       *   If {@code null} simple type cast will be used for char to byte conversion.
90       * @param incomingContentStrategy incoming content length strategy. If {@code null}
91       *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
92       * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
93       *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
94       * @param responseOutOfOrderStrategy response out of order strategy. If {@code null}
95       *   {@link NoResponseOutOfOrderStrategy#INSTANCE} will be used.
96       * @param requestWriterFactory request writer factory. If {@code null}
97       *   {@link DefaultHttpRequestWriterFactory#INSTANCE} will be used.
98       * @param responseParserFactory response parser factory. If {@code null}
99       *   {@link DefaultHttpResponseParserFactory#INSTANCE} will be used.
100      */
101     public DefaultBHttpClientConnection(
102             final Http1Config http1Config,
103             final CharsetDecoder charDecoder,
104             final CharsetEncoder charEncoder,
105             final ContentLengthStrategy incomingContentStrategy,
106             final ContentLengthStrategy outgoingContentStrategy,
107             final ResponseOutOfOrderStrategy responseOutOfOrderStrategy,
108             final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
109             final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
110         super(http1Config, charDecoder, charEncoder);
111         this.requestWriter = (requestWriterFactory != null ? requestWriterFactory :
112             DefaultHttpRequestWriterFactory.INSTANCE).create();
113         this.responseParser = (responseParserFactory != null ? responseParserFactory :
114             DefaultHttpResponseParserFactory.INSTANCE).create();
115         this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy :
116             DefaultContentLengthStrategy.INSTANCE;
117         this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy :
118             DefaultContentLengthStrategy.INSTANCE;
119         this.responseOutOfOrderStrategy = responseOutOfOrderStrategy;
120         this.consistent = true;
121     }
122 
123     /**
124      * Creates new instance of DefaultBHttpClientConnection.
125      *
126      * @param http1Config Message http1Config. If {@code null}
127      *   {@link Http1Config#DEFAULT} will be used.
128      * @param charDecoder decoder to be used for decoding HTTP protocol elements.
129      *   If {@code null} simple type cast will be used for byte to char conversion.
130      * @param charEncoder encoder to be used for encoding HTTP protocol elements.
131      *   If {@code null} simple type cast will be used for char to byte conversion.
132      * @param incomingContentStrategy incoming content length strategy. If {@code null}
133      *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
134      * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
135      *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
136      * @param requestWriterFactory request writer factory. If {@code null}
137      *   {@link DefaultHttpRequestWriterFactory#INSTANCE} will be used.
138      * @param responseParserFactory response parser factory. If {@code null}
139      *   {@link DefaultHttpResponseParserFactory#INSTANCE} will be used.
140      */
141     public DefaultBHttpClientConnection(
142             final Http1Config http1Config,
143             final CharsetDecoder charDecoder,
144             final CharsetEncoder charEncoder,
145             final ContentLengthStrategy incomingContentStrategy,
146             final ContentLengthStrategy outgoingContentStrategy,
147             final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
148             final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
149         this(
150                 http1Config,
151                 charDecoder,
152                 charEncoder,
153                 incomingContentStrategy,
154                 outgoingContentStrategy,
155                 null,
156                 requestWriterFactory,
157                 responseParserFactory);
158     }
159 
160     public DefaultBHttpClientConnection(
161             final Http1Config http1Config,
162             final CharsetDecoder charDecoder,
163             final CharsetEncoder charEncoder) {
164         this(http1Config, charDecoder, charEncoder, null, null, null, null);
165     }
166 
167     public DefaultBHttpClientConnection(final Http1Config http1Config) {
168         this(http1Config, null, null);
169     }
170 
171     protected void onResponseReceived(final ClassicHttpResponse response) {
172     }
173 
174     protected void onRequestSubmitted(final ClassicHttpRequest request) {
175     }
176 
177     @Override
178     public void bind(final Socket socket) throws IOException {
179         super.bind(socket);
180     }
181 
182     /**
183      * @throws IOException in case of an I/O error.
184      * @since 5.3
185      */
186     public void bind(final SSLSocket sslSocket, final Socket baseSocket) throws IOException {
187         super.bind(new SocketHolder(sslSocket, baseSocket));
188     }
189 
190     @Override
191     public void sendRequestHeader(final ClassicHttpRequest request)
192             throws HttpException, IOException {
193         Args.notNull(request, "HTTP request");
194         final SocketHolder socketHolder = ensureOpen();
195         this.requestWriter.write(request, this.outbuffer, socketHolder.getOutputStream());
196         onRequestSubmitted(request);
197         incrementRequestCount();
198     }
199 
200     @Override
201     public void sendRequestEntity(final ClassicHttpRequest request) throws HttpException, IOException {
202         Args.notNull(request, "HTTP request");
203         final SocketHolder socketHolder = ensureOpen();
204         final HttpEntity entity = request.getEntity();
205         if (entity == null) {
206             return;
207         }
208         final long len = this.outgoingContentStrategy.determineLength(request);
209         if (len == ContentLengthStrategy.UNDEFINED) {
210             throw new LengthRequiredException();
211         }
212         try (final OutputStream outStream = createContentOutputStream(
213                 len, this.outbuffer, new OutputStream() {
214 
215                     final OutputStream socketOutputStream = socketHolder.getOutputStream();
216                     final InputStream socketInputStream = socketHolder.getInputStream();
217                     final SSLSocket sslSocket = socketHolder.getSSLSocket();
218 
219                     long totalBytes;
220 
221                     void checkTLS(final SSLSocket sslSocket) throws IOException {
222                         if (sslSocket.isInputShutdown()) {
223                             throw new ConnectionClosedException();
224                         }
225                     }
226 
227                     void checkForEarlyResponse(final long totalBytesSent, final int nextWriteSize) throws IOException {
228                         if (responseOutOfOrderStrategy.isEarlyResponseDetected(
229                                 request,
230                                 DefaultBHttpClientConnection.this,
231                                 socketInputStream,
232                                 totalBytesSent,
233                                 nextWriteSize)) {
234                             throw new ResponseOutOfOrderException();
235                         }
236                     }
237 
238                     @Override
239                     public void write(final byte[] b) throws IOException {
240                         if (sslSocket != null) {
241                             checkTLS(sslSocket);
242                         }
243                         if (responseOutOfOrderStrategy != null) {
244                             checkForEarlyResponse(totalBytes, b.length);
245                         }
246                         totalBytes += b.length;
247                         socketOutputStream.write(b);
248                     }
249 
250                     @Override
251                     public void write(final byte[] b, final int off, final int len) throws IOException {
252                         if (sslSocket != null) {
253                             checkTLS(sslSocket);
254                         }
255                         if (responseOutOfOrderStrategy != null) {
256                             checkForEarlyResponse(totalBytes, len);
257                         }
258                         totalBytes += len;
259                         socketOutputStream.write(b, off, len);
260                     }
261 
262                     @Override
263                     public void write(final int b) throws IOException {
264                         if (sslSocket != null) {
265                             checkTLS(sslSocket);
266                         }
267                         if (responseOutOfOrderStrategy != null) {
268                             checkForEarlyResponse(totalBytes, 1);
269                         }
270                         totalBytes++;
271                         socketOutputStream.write(b);
272                     }
273 
274                     @Override
275                     public void flush() throws IOException {
276                         socketOutputStream.flush();
277                     }
278 
279                     @Override
280                     public void close() throws IOException {
281                         socketOutputStream.close();
282                     }
283 
284                 }, entity.getTrailers())) {
285             entity.writeTo(outStream);
286         } catch (final ResponseOutOfOrderException ex) {
287             if (len > 0) {
288                 this.consistent = false;
289             }
290         }
291     }
292 
293     @Override
294     public boolean isConsistent() {
295         return this.consistent;
296     }
297 
298     @Override
299     public void terminateRequest(final ClassicHttpRequest request) throws HttpException, IOException {
300         Args.notNull(request, "HTTP request");
301         final SocketHolder socketHolder = ensureOpen();
302         final HttpEntity entity = request.getEntity();
303         if (entity == null) {
304             return;
305         }
306         final Iterator<String> it = MessageSupport.iterateTokens(request, HttpHeaders.CONNECTION);
307         while (it.hasNext()) {
308             final String token = it.next();
309             if (HeaderElements.CLOSE.equalsIgnoreCase(token)) {
310                 this.consistent = false;
311                 return;
312             }
313         }
314         final long len = this.outgoingContentStrategy.determineLength(request);
315         if (len == ContentLengthStrategy.CHUNKED) {
316             try (final OutputStream outStream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), entity.getTrailers())) {
317                 // just close
318             }
319         } else if (len >= 0 && len <= 1024) {
320             try (final OutputStream outStream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), null)) {
321                 entity.writeTo(outStream);
322             }
323         } else {
324             this.consistent = false;
325         }
326     }
327 
328     @Override
329     public ClassicHttpResponse receiveResponseHeader() throws HttpException, IOException {
330         final SocketHolder socketHolder = ensureOpen();
331         final ClassicHttpResponse response = this.responseParser.parse(this.inBuffer, socketHolder.getInputStream());
332         if (response == null) {
333             throw new NoHttpResponseException("The target server failed to respond");
334         }
335         final ProtocolVersion transportVersion = response.getVersion();
336         if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) {
337             throw new UnsupportedHttpVersionException(transportVersion);
338         }
339         this.version = transportVersion;
340         onResponseReceived(response);
341         final int status = response.getCode();
342         if (status < HttpStatus.SC_INFORMATIONAL) {
343             throw new ProtocolException("Invalid response: " + status);
344         }
345         if (response.getCode() >= HttpStatus.SC_SUCCESS) {
346             incrementResponseCount();
347         }
348         return response;
349     }
350 
351     @Override
352     public void receiveResponseEntity( final ClassicHttpResponse response) throws HttpException, IOException {
353         Args.notNull(response, "HTTP response");
354         final SocketHolder socketHolder = ensureOpen();
355         final long len = this.incomingContentStrategy.determineLength(response);
356         response.setEntity(createIncomingEntity(response, this.inBuffer, socketHolder.getInputStream(), len));
357     }
358 }