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  
32  import org.apache.hc.core5.annotation.Contract;
33  import org.apache.hc.core5.annotation.ThreadingBehavior;
34  import org.apache.hc.core5.http.ClassicHttpRequest;
35  import org.apache.hc.core5.http.ClassicHttpResponse;
36  import org.apache.hc.core5.http.ConnectionReuseStrategy;
37  import org.apache.hc.core5.http.Header;
38  import org.apache.hc.core5.http.HttpEntity;
39  import org.apache.hc.core5.http.HttpException;
40  import org.apache.hc.core5.http.HttpHeaders;
41  import org.apache.hc.core5.http.HttpStatus;
42  import org.apache.hc.core5.http.HttpVersion;
43  import org.apache.hc.core5.http.ProtocolException;
44  import org.apache.hc.core5.http.ProtocolVersion;
45  import org.apache.hc.core5.http.UnsupportedHttpVersionException;
46  import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
47  import org.apache.hc.core5.http.impl.Http1StreamListener;
48  import org.apache.hc.core5.http.io.HttpClientConnection;
49  import org.apache.hc.core5.http.io.HttpResponseInformationCallback;
50  import org.apache.hc.core5.http.message.MessageSupport;
51  import org.apache.hc.core5.http.message.StatusLine;
52  import org.apache.hc.core5.http.protocol.HttpContext;
53  import org.apache.hc.core5.http.protocol.HttpCoreContext;
54  import org.apache.hc.core5.http.protocol.HttpProcessor;
55  import org.apache.hc.core5.util.Args;
56  import org.apache.hc.core5.io.Closer;
57  
58  /**
59   * {@code HttpRequestExecutor} is a client side HTTP protocol handler based
60   * on the blocking (classic) I/O model.
61   * <p>
62   * {@code HttpRequestExecutor} relies on {@link HttpProcessor} to generate
63   * mandatory protocol headers for all outgoing messages and apply common,
64   * cross-cutting message transformations to all incoming and outgoing messages.
65   * Application specific processing can be implemented outside
66   * {@code HttpRequestExecutor} once the request has been executed and
67   * a response has been received.
68   *
69   * @since 4.0
70   */
71  @Contract(threading = ThreadingBehavior.IMMUTABLE)
72  public class HttpRequestExecutor {
73  
74      public static final int DEFAULT_WAIT_FOR_CONTINUE = 3000;
75  
76      private final int waitForContinue;
77      private final ConnectionReuseStrategy connReuseStrategy;
78      private final Http1StreamListener streamListener;
79  
80      /**
81       * Creates new instance of HttpRequestExecutor.
82       *
83       * @since 4.3
84       */
85      public HttpRequestExecutor(
86              final int waitForContinue,
87              final ConnectionReuseStrategy connReuseStrategy,
88              final Http1StreamListener streamListener) {
89          super();
90          this.waitForContinue = Args.positive(waitForContinue, "Wait for continue time");
91          this.connReuseStrategy = connReuseStrategy != null ? connReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE;
92          this.streamListener = streamListener;
93      }
94  
95      public HttpRequestExecutor(final ConnectionReuseStrategy connReuseStrategy) {
96          this(DEFAULT_WAIT_FOR_CONTINUE, connReuseStrategy, null);
97      }
98  
99      public HttpRequestExecutor() {
100         this(DEFAULT_WAIT_FOR_CONTINUE, null, null);
101     }
102 
103     /**
104      * Sends the request and obtain a response.
105      *
106      * @param request   the request to execute.
107      * @param conn      the connection over which to execute the request.
108      * @param informationCallback   callback to execute upon receipt of information status (1xx).
109      *                              May be null.
110      * @param context the context
111      * @return  the response to the request.
112      *
113      * @throws IOException in case of an I/O error.
114      * @throws HttpException in case of HTTP protocol violation or a processing
115      *   problem.
116      */
117     public ClassicHttpResponse execute(
118             final ClassicHttpRequest request,
119             final HttpClientConnection conn,
120             final HttpResponseInformationCallback informationCallback,
121             final HttpContext context) throws IOException, HttpException {
122         Args.notNull(request, "HTTP request");
123         Args.notNull(conn, "Client connection");
124         Args.notNull(context, "HTTP context");
125         try {
126             context.setAttribute(HttpCoreContext.SSL_SESSION, conn.getSSLSession());
127             context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, conn.getEndpointDetails());
128             final ProtocolVersion transportVersion = request.getVersion();
129             if (transportVersion != null) {
130                 if (transportVersion.greaterEquals(HttpVersion.HTTP_2)) {
131                     throw new UnsupportedHttpVersionException(transportVersion);
132                 }
133                 context.setProtocolVersion(transportVersion);
134             }
135 
136             conn.sendRequestHeader(request);
137             if (streamListener != null) {
138                 streamListener.onRequestHead(conn, request);
139             }
140             boolean expectContinue = false;
141             final HttpEntity entity = request.getEntity();
142             if (entity != null) {
143                 final Header expect = request.getFirstHeader(HttpHeaders.EXPECT);
144                 expectContinue = expect != null && "100-continue".equalsIgnoreCase(expect.getValue());
145                 if (!expectContinue) {
146                     conn.sendRequestEntity(request);
147                 }
148             }
149             conn.flush();
150             ClassicHttpResponse response = null;
151             while (response == null) {
152                 if (expectContinue) {
153                     if (conn.isDataAvailable(this.waitForContinue)) {
154                         response = conn.receiveResponseHeader();
155                         if (streamListener != null) {
156                             streamListener.onResponseHead(conn, response);
157                         }
158                         final int status = response.getCode();
159                         if (status == HttpStatus.SC_CONTINUE) {
160                             // discard 100-continue
161                             response = null;
162                             conn.sendRequestEntity(request);
163                         } else if (status < HttpStatus.SC_SUCCESS) {
164                             if (informationCallback != null) {
165                                 informationCallback.execute(response, conn, context);
166                             }
167                             response = null;
168                             continue;
169                         } else if (status >= HttpStatus.SC_CLIENT_ERROR){
170                             conn.terminateRequest(request);
171                         } else {
172                             conn.sendRequestEntity(request);
173                         }
174                     } else {
175                         conn.sendRequestEntity(request);
176                     }
177                     conn.flush();
178                     expectContinue = false;
179                 } else {
180                     response = conn.receiveResponseHeader();
181                     if (streamListener != null) {
182                         streamListener.onResponseHead(conn, response);
183                     }
184                     final int status = response.getCode();
185                     if (status < HttpStatus.SC_INFORMATIONAL) {
186                         throw new ProtocolException("Invalid response: " + new StatusLine(response));
187                     }
188                     if (status < HttpStatus.SC_SUCCESS) {
189                         if (informationCallback != null && status != HttpStatus.SC_CONTINUE) {
190                             informationCallback.execute(response, conn, context);
191                         }
192                         response = null;
193                     }
194                 }
195             }
196             if (MessageSupport.canResponseHaveBody(request.getMethod(), response)) {
197                 conn.receiveResponseEntity(response);
198             }
199             return response;
200 
201         } catch (final HttpException | IOException | RuntimeException ex) {
202             Closer.closeQuietly(conn);
203             throw ex;
204         }
205     }
206 
207     /**
208      * Sends the request and obtain a response.
209      *
210      * @param request   the request to execute.
211      * @param conn      the connection over which to execute the request.
212      * @param context the context
213      * @return  the response to the request.
214      *
215      * @throws IOException in case of an I/O error.
216      * @throws HttpException in case of HTTP protocol violation or a processing
217      *   problem.
218      */
219     public ClassicHttpResponse execute(
220             final ClassicHttpRequest request,
221             final HttpClientConnection conn,
222             final HttpContext context) throws IOException, HttpException {
223         return execute(request, conn, null, context);
224     }
225 
226     /**
227      * Pre-process the given request using the given protocol processor and
228      * initiates the process of request execution.
229      *
230      * @param request   the request to prepare
231      * @param processor the processor to use
232      * @param context   the context for sending the request
233      *
234      * @throws IOException in case of an I/O error.
235      * @throws HttpException in case of HTTP protocol violation or a processing
236      *   problem.
237      */
238     public void preProcess(
239             final ClassicHttpRequest request,
240             final HttpProcessor processor,
241             final HttpContext context) throws HttpException, IOException {
242         Args.notNull(request, "HTTP request");
243         Args.notNull(processor, "HTTP processor");
244         Args.notNull(context, "HTTP context");
245         context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
246         processor.process(request, request.getEntity(), context);
247     }
248 
249     /**
250      * Post-processes the given response using the given protocol processor and
251      * completes the process of request execution.
252      * <p>
253      * This method does <i>not</i> read the response entity, if any.
254      * The connection over which content of the response entity is being
255      * streamed from cannot be reused until the response entity has been
256      * fully consumed.
257      *
258      * @param response  the response object to post-process
259      * @param processor the processor to use
260      * @param context   the context for post-processing the response
261      *
262      * @throws IOException in case of an I/O error.
263      * @throws HttpException in case of HTTP protocol violation or a processing
264      *   problem.
265      */
266     public void postProcess(
267             final ClassicHttpResponse response,
268             final HttpProcessor processor,
269             final HttpContext context) throws HttpException, IOException {
270         Args.notNull(response, "HTTP response");
271         Args.notNull(processor, "HTTP processor");
272         Args.notNull(context, "HTTP context");
273         final ProtocolVersion transportVersion = response.getVersion();
274         if (transportVersion != null) {
275             context.setProtocolVersion(transportVersion);
276         }
277         context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
278         processor.process(response, response.getEntity(), context);
279     }
280 
281     /**
282      * Determines whether the connection can be kept alive and is safe to be re-used for subsequent message exchanges.
283      *
284      * @param request current request object.
285      * @param response  current response object.
286      * @param connection actual connection.
287      * @param context current context.
288      * @return {@code true} is the connection can be kept-alive and re-used.
289      * @throws IOException in case of an I/O error.
290      */
291     public boolean keepAlive(
292             final ClassicHttpRequest request,
293             final ClassicHttpResponse response,
294             final HttpClientConnection connection,
295             final HttpContext context) throws IOException {
296         Args.notNull(connection, "HTTP connection");
297         Args.notNull(request, "HTTP request");
298         Args.notNull(response, "HTTP response");
299         Args.notNull(context, "HTTP context");
300         final boolean keepAlive = connection.isConsistent() && connReuseStrategy.keepAlive(request, response, context);
301         if (streamListener != null) {
302             streamListener.onExchangeComplete(connection, keepAlive);
303         }
304         return keepAlive;
305     }
306 
307 }