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      * @since 5.3
184      */
185     public void bind(final SSLSocket sslSocket, final Socket baseSocket) throws IOException {
186         super.bind(new SocketHolder(sslSocket, baseSocket));
187     }
188 
189     @Override
190     public void sendRequestHeader(final ClassicHttpRequest request)
191             throws HttpException, IOException {
192         Args.notNull(request, "HTTP request");
193         final SocketHolder socketHolder = ensureOpen();
194         this.requestWriter.write(request, this.outbuffer, socketHolder.getOutputStream());
195         onRequestSubmitted(request);
196         incrementRequestCount();
197     }
198 
199     @Override
200     public void sendRequestEntity(final ClassicHttpRequest request) throws HttpException, IOException {
201         Args.notNull(request, "HTTP request");
202         final SocketHolder socketHolder = ensureOpen();
203         final HttpEntity entity = request.getEntity();
204         if (entity == null) {
205             return;
206         }
207         final long len = this.outgoingContentStrategy.determineLength(request);
208         if (len == ContentLengthStrategy.UNDEFINED) {
209             throw new LengthRequiredException();
210         }
211         try (final OutputStream outStream = createContentOutputStream(
212                 len, this.outbuffer, new OutputStream() {
213 
214                     final OutputStream socketOutputStream = socketHolder.getOutputStream();
215                     final InputStream socketInputStream = socketHolder.getInputStream();
216                     final SSLSocket sslSocket = socketHolder.getSSLSocket();
217 
218                     long totalBytes;
219 
220                     void checkTLS(final SSLSocket sslSocket) throws IOException {
221                         if (sslSocket.isInputShutdown()) {
222                             throw new ConnectionClosedException();
223                         }
224                     }
225 
226                     void checkForEarlyResponse(final long totalBytesSent, final int nextWriteSize) throws IOException {
227                         if (responseOutOfOrderStrategy.isEarlyResponseDetected(
228                                 request,
229                                 DefaultBHttpClientConnection.this,
230                                 socketInputStream,
231                                 totalBytesSent,
232                                 nextWriteSize)) {
233                             throw new ResponseOutOfOrderException();
234                         }
235                     }
236 
237                     @Override
238                     public void write(final byte[] b) throws IOException {
239                         if (sslSocket != null) {
240                             checkTLS(sslSocket);
241                         }
242                         if (responseOutOfOrderStrategy != null) {
243                             checkForEarlyResponse(totalBytes, b.length);
244                         }
245                         totalBytes += b.length;
246                         socketOutputStream.write(b);
247                     }
248 
249                     @Override
250                     public void write(final byte[] b, final int off, final int len) throws IOException {
251                         if (sslSocket != null) {
252                             checkTLS(sslSocket);
253                         }
254                         if (responseOutOfOrderStrategy != null) {
255                             checkForEarlyResponse(totalBytes, len);
256                         }
257                         totalBytes += len;
258                         socketOutputStream.write(b, off, len);
259                     }
260 
261                     @Override
262                     public void write(final int b) throws IOException {
263                         if (sslSocket != null) {
264                             checkTLS(sslSocket);
265                         }
266                         if (responseOutOfOrderStrategy != null) {
267                             checkForEarlyResponse(totalBytes, 1);
268                         }
269                         totalBytes++;
270                         socketOutputStream.write(b);
271                     }
272 
273                     @Override
274                     public void flush() throws IOException {
275                         socketOutputStream.flush();
276                     }
277 
278                     @Override
279                     public void close() throws IOException {
280                         socketOutputStream.close();
281                     }
282 
283                 }, entity.getTrailers())) {
284             entity.writeTo(outStream);
285         } catch (final ResponseOutOfOrderException ex) {
286             if (len > 0) {
287                 this.consistent = false;
288             }
289         }
290     }
291 
292     @Override
293     public boolean isConsistent() {
294         return this.consistent;
295     }
296 
297     @Override
298     public void terminateRequest(final ClassicHttpRequest request) throws HttpException, IOException {
299         Args.notNull(request, "HTTP request");
300         final SocketHolder socketHolder = ensureOpen();
301         final HttpEntity entity = request.getEntity();
302         if (entity == null) {
303             return;
304         }
305         final Iterator<String> it = MessageSupport.iterateTokens(request, HttpHeaders.CONNECTION);
306         while (it.hasNext()) {
307             final String token = it.next();
308             if (HeaderElements.CLOSE.equalsIgnoreCase(token)) {
309                 this.consistent = false;
310                 return;
311             }
312         }
313         final long len = this.outgoingContentStrategy.determineLength(request);
314         if (len == ContentLengthStrategy.CHUNKED) {
315             try (final OutputStream outStream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), entity.getTrailers())) {
316                 // just close
317             }
318         } else if (len >= 0 && len <= 1024) {
319             try (final OutputStream outStream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), null)) {
320                 entity.writeTo(outStream);
321             }
322         } else {
323             this.consistent = false;
324         }
325     }
326 
327     @Override
328     public ClassicHttpResponse receiveResponseHeader() throws HttpException, IOException {
329         final SocketHolder socketHolder = ensureOpen();
330         final ClassicHttpResponse response = this.responseParser.parse(this.inBuffer, socketHolder.getInputStream());
331         if (response == null) {
332             throw new NoHttpResponseException("The target server failed to respond");
333         }
334         final ProtocolVersion transportVersion = response.getVersion();
335         if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) {
336             throw new UnsupportedHttpVersionException(transportVersion);
337         }
338         this.version = transportVersion;
339         onResponseReceived(response);
340         final int status = response.getCode();
341         if (status < HttpStatus.SC_INFORMATIONAL) {
342             throw new ProtocolException("Invalid response: " + status);
343         }
344         if (response.getCode() >= HttpStatus.SC_SUCCESS) {
345             incrementResponseCount();
346         }
347         return response;
348     }
349 
350     @Override
351     public void receiveResponseEntity( final ClassicHttpResponse response) throws HttpException, IOException {
352         Args.notNull(response, "HTTP response");
353         final SocketHolder socketHolder = ensureOpen();
354         final long len = this.incomingContentStrategy.determineLength(response);
355         response.setEntity(createIncomingEntity(response, this.inBuffer, socketHolder.getInputStream(), len));
356     }
357 }