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