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