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