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.OutputStream;
32  import java.net.Socket;
33  import java.nio.charset.CharsetDecoder;
34  import java.nio.charset.CharsetEncoder;
35  
36  import org.apache.hc.core5.http.ClassicHttpRequest;
37  import org.apache.hc.core5.http.ClassicHttpResponse;
38  import org.apache.hc.core5.http.ContentLengthStrategy;
39  import org.apache.hc.core5.http.HttpEntity;
40  import org.apache.hc.core5.http.HttpException;
41  import org.apache.hc.core5.http.HttpStatus;
42  import org.apache.hc.core5.http.HttpVersion;
43  import org.apache.hc.core5.http.LengthRequiredException;
44  import org.apache.hc.core5.http.ProtocolException;
45  import org.apache.hc.core5.http.ProtocolVersion;
46  import org.apache.hc.core5.http.UnsupportedHttpVersionException;
47  import org.apache.hc.core5.http.config.H1Config;
48  import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
49  import org.apache.hc.core5.http.io.HttpClientConnection;
50  import org.apache.hc.core5.http.io.HttpMessageParser;
51  import org.apache.hc.core5.http.io.HttpMessageParserFactory;
52  import org.apache.hc.core5.http.io.HttpMessageWriter;
53  import org.apache.hc.core5.http.io.HttpMessageWriterFactory;
54  import org.apache.hc.core5.util.Args;
55  
56  /**
57   * Default implementation of {@link HttpClientConnection}.
58   *
59   * @since 4.3
60   */
61  public class DefaultBHttpClientConnection extends BHttpConnectionBase
62                                                     implements HttpClientConnection {
63  
64      private final HttpMessageParser<ClassicHttpResponse> responseParser;
65      private final HttpMessageWriter<ClassicHttpRequest> requestWriter;
66      private final ContentLengthStrategy incomingContentStrategy;
67      private final ContentLengthStrategy outgoingContentStrategy;
68      private volatile boolean consistent;
69  
70      /**
71       * Creates new instance of DefaultBHttpClientConnection.
72       *
73       * @param h1Config Message h1Config. If {@code null}
74       *   {@link H1Config#DEFAULT} will be used.
75       * @param chardecoder decoder to be used for decoding HTTP protocol elements.
76       *   If {@code null} simple type cast will be used for byte to char conversion.
77       * @param charencoder encoder to be used for encoding HTTP protocol elements.
78       *   If {@code null} simple type cast will be used for char to byte conversion.
79       * @param incomingContentStrategy incoming content length strategy. If {@code null}
80       *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
81       * @param outgoingContentStrategy outgoing content length strategy. If {@code null}
82       *   {@link DefaultContentLengthStrategy#INSTANCE} will be used.
83       * @param requestWriterFactory request writer factory. If {@code null}
84       *   {@link DefaultHttpRequestWriterFactory#INSTANCE} will be used.
85       * @param responseParserFactory response parser factory. If {@code null}
86       *   {@link DefaultHttpResponseParserFactory#INSTANCE} will be used.
87       */
88      public DefaultBHttpClientConnection(
89              final H1Config h1Config,
90              final CharsetDecoder chardecoder,
91              final CharsetEncoder charencoder,
92              final ContentLengthStrategy incomingContentStrategy,
93              final ContentLengthStrategy outgoingContentStrategy,
94              final HttpMessageWriterFactory<ClassicHttpRequest> requestWriterFactory,
95              final HttpMessageParserFactory<ClassicHttpResponse> responseParserFactory) {
96          super(h1Config, chardecoder, charencoder);
97          this.requestWriter = (requestWriterFactory != null ? requestWriterFactory :
98              DefaultHttpRequestWriterFactory.INSTANCE).create();
99          this.responseParser = (responseParserFactory != null ? responseParserFactory :
100             DefaultHttpResponseParserFactory.INSTANCE).create(h1Config);
101         this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy :
102                 DefaultContentLengthStrategy.INSTANCE;
103         this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy :
104                 DefaultContentLengthStrategy.INSTANCE;
105         this.consistent = true;
106     }
107 
108     public DefaultBHttpClientConnection(
109             final H1Config h1Config,
110             final CharsetDecoder chardecoder,
111             final CharsetEncoder charencoder) {
112         this(h1Config, chardecoder, charencoder, null, null, null, null);
113     }
114 
115     public DefaultBHttpClientConnection(final H1Config h1Config) {
116         this(h1Config, null, null);
117     }
118 
119     protected void onResponseReceived(final ClassicHttpResponse response) {
120     }
121 
122     protected void onRequestSubmitted(final ClassicHttpRequest request) {
123     }
124 
125     @Override
126     public void bind(final Socket socket) throws IOException {
127         super.bind(socket);
128     }
129 
130     @Override
131     public void sendRequestHeader(final ClassicHttpRequest request)
132             throws HttpException, IOException {
133         Args.notNull(request, "HTTP request");
134         final SocketHolder socketHolder = ensureOpen();
135         this.requestWriter.write(request, this.outbuffer, socketHolder.getOutputStream());
136         onRequestSubmitted(request);
137         incrementRequestCount();
138     }
139 
140     @Override
141     public void sendRequestEntity(final ClassicHttpRequest request) throws HttpException, IOException {
142         Args.notNull(request, "HTTP request");
143         final SocketHolder socketHolder = ensureOpen();
144         final HttpEntity entity = request.getEntity();
145         if (entity == null) {
146             return;
147         }
148         final long len = this.outgoingContentStrategy.determineLength(request);
149         if (len == ContentLengthStrategy.UNDEFINED) {
150             throw new LengthRequiredException("Length required");
151         }
152         try (final OutputStream outstream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), entity.getTrailers())) {
153             entity.writeTo(outstream);
154         }
155     }
156 
157     @Override
158     public boolean isConsistent() {
159         return this.consistent;
160     }
161 
162     @Override
163     public void terminateRequest(final ClassicHttpRequest request) throws HttpException, IOException {
164         Args.notNull(request, "HTTP request");
165         final SocketHolder socketHolder = ensureOpen();
166         final HttpEntity entity = request.getEntity();
167         if (entity == null) {
168             return;
169         }
170         final long len = this.outgoingContentStrategy.determineLength(request);
171         if (len == ContentLengthStrategy.CHUNKED) {
172             final OutputStream outstream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), entity.getTrailers());
173             outstream.close();
174         } else if (len >= 0 && len <= 1024) {
175             try (final OutputStream outstream = createContentOutputStream(len, this.outbuffer, socketHolder.getOutputStream(), null)) {
176                 entity.writeTo(outstream);
177             }
178         } else {
179             this.consistent = false;
180         }
181     }
182 
183     @Override
184     public ClassicHttpResponse receiveResponseHeader() throws HttpException, IOException {
185         final SocketHolder socketHolder = ensureOpen();
186         final ClassicHttpResponse response = this.responseParser.parse(this.inbuffer, socketHolder.getInputStream());
187         final ProtocolVersion transportVersion = response.getVersion();
188         if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) {
189             throw new UnsupportedHttpVersionException("Unsupported version: " + transportVersion);
190         }
191         this.version = transportVersion;
192         onResponseReceived(response);
193         final int status = response.getCode();
194         if (status < HttpStatus.SC_INFORMATIONAL) {
195             throw new ProtocolException("Invalid response: " + status);
196         }
197         if (response.getCode() >= HttpStatus.SC_SUCCESS) {
198             incrementResponseCount();
199         }
200         return response;
201     }
202 
203     @Override
204     public void receiveResponseEntity( final ClassicHttpResponse response) throws HttpException, IOException {
205         Args.notNull(response, "HTTP response");
206         final SocketHolder socketHolder = ensureOpen();
207         final long len = this.incomingContentStrategy.determineLength(response);
208         if (len == ContentLengthStrategy.UNDEFINED) {
209             return;
210         }
211         response.setEntity(createIncomingEntity(response, this.inbuffer, socketHolder.getInputStream(), len));
212     }
213 }