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