Source code: org/apache/http/protocol/HttpRequestExecutor.java
1 /*
2 * $HeadURL: https://svn.apache.org/repos/asf/jakarta/httpcomponents/httpcore/tags/4.0-alpha2/src/java/org/apache/http/protocol/HttpRequestExecutor.java $
3 * $Revision: 385867 $
4 * $Date: 2006-03-14 21:04:55 +0100 (Tue, 14 Mar 2006) $
5 *
6 * ====================================================================
7 *
8 * Copyright 1999-2006 The Apache Software Foundation
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 * ====================================================================
22 *
23 * This software consists of voluntary contributions made by many
24 * individuals on behalf of the Apache Software Foundation. For more
25 * information on the Apache Software Foundation, please see
26 * <http://www.apache.org/>.
27 *
28 */
29
30 package org.apache.http.protocol;
31
32 import java.io.IOException;
33 import java.net.ProtocolException;
34
35 import org.apache.http.HttpClientConnection;
36 import org.apache.http.HttpEntityEnclosingRequest;
37 import org.apache.http.HttpException;
38 import org.apache.http.HttpHost;
39 import org.apache.http.HttpRequest;
40 import org.apache.http.HttpResponse;
41 import org.apache.http.HttpStatus;
42 import org.apache.http.HttpVersion;
43 import org.apache.http.params.HttpConnectionParams;
44 import org.apache.http.params.HttpParams;
45
46 /**
47 * Sends HTTP requests and receives the responses.
48 * Takes care of request preprocessing and response postprocessing
49 * by the respective interceptors.
50 *
51 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
52 *
53 * @version $Revision: 385867 $
54 *
55 * @since 4.0
56 */
57 public class HttpRequestExecutor extends AbstractHttpProcessor {
58
59 protected static final int WAIT_FOR_CONTINUE_MS = 10000;
60
61 /** The context holding the default context information. */
62 protected final HttpContext defaultContext;
63
64 private HttpParams params = null;
65 private HttpRequestRetryHandler retryhandler = null;
66
67 /**
68 * Create a new request executor with default context information.
69 * The attributes in the argument context will be made available
70 * in the context used for executing a request.
71 *
72 * @param parentContext the default context information,
73 * or <code>null</code>
74 */
75 public HttpRequestExecutor(final HttpContext parentContext) {
76 super();
77 this.defaultContext = new HttpExecutionContext(parentContext);
78 }
79
80 /**
81 * Create a new request executor.
82 */
83 public HttpRequestExecutor() {
84 this(null);
85 }
86
87 /**
88 * Obtain the default context information.
89 * This is not necessarily the same object passed to the constructor,
90 * but the default context information will be available here.
91 *
92 * @return the context holding the default context information
93 */
94 public final HttpContext getContext() {
95 return this.defaultContext;
96 }
97
98 /**
99 * Obtain the parameters for executing requests.
100 *
101 * @return the currently installed parameters
102 */
103 public final HttpParams getParams() {
104 return this.params;
105 }
106
107 /**
108 * Set new parameters for executing requests.
109 *
110 * @param params the new parameters to use from now on
111 */
112 public final void setParams(final HttpParams params) {
113 this.params = params;
114 }
115
116 /**
117 * Obtain the retry handler.
118 *
119 * @return the handler deciding whether a request should be retried
120 */
121 public final HttpRequestRetryHandler getRetryHandler() {
122 return this.retryhandler;
123 }
124
125 /**
126 * Set the retry handler.
127 *
128 * @param retryhandler the handler to decide whether a request
129 * should be retried
130 */
131 public final void setRetryHandler(final HttpRequestRetryHandler retryhandler) {
132 this.retryhandler = retryhandler;
133 }
134
135 /**
136 * Decide whether a response comes with an entity.
137 * The implementation in this class is based on RFC 2616.
138 * Unknown methods and response codes are supposed to
139 * indicate responses with an entity.
140 * <br/>
141 * Derived executors can override this method to handle
142 * methods and response codes not specified in RFC 2616.
143 *
144 * @param request the request, to obtain the executed method
145 * @param response the response, to obtain the status code
146 */
147 protected boolean canResponseHaveBody(final HttpRequest request,
148 final HttpResponse response) {
149
150 if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
151 return false;
152 }
153 int status = response.getStatusLine().getStatusCode();
154 return status >= HttpStatus.SC_OK
155 && status != HttpStatus.SC_NO_CONTENT
156 && status != HttpStatus.SC_NOT_MODIFIED
157 && status != HttpStatus.SC_RESET_CONTENT;
158 }
159
160 /**
161 * Synchronously send a request and obtain the response.
162 *
163 * @param request the request to send. It will be preprocessed.
164 * @param conn the connection over which to send.
165 * The {@link HttpClientConnection#setTargetHost target}
166 * host has to be set before calling this method.
167 *
168 * @return the response to the request, postprocessed
169 *
170 * @throws HttpException in case of a protocol or processing problem
171 * @throws IOException in case of an I/O problem
172 */
173 public HttpResponse execute(
174 final HttpRequest request,
175 final HttpClientConnection conn)
176 throws IOException, HttpException {
177 if (request == null) {
178 throw new IllegalArgumentException("HTTP request may not be null");
179 }
180 if (conn == null) {
181 throw new IllegalArgumentException("Client connection may not be null");
182 }
183
184 //@@@ behavior if proxying - set real target or proxy, or both?
185 this.defaultContext.setAttribute(HttpExecutionContext.HTTP_TARGET_HOST,
186 conn.getTargetHost());
187 this.defaultContext.setAttribute(HttpExecutionContext.HTTP_CONNECTION,
188 conn);
189
190 doPrepareRequest(request, this.defaultContext);
191
192 this.defaultContext.setAttribute(HttpExecutionContext.HTTP_REQUEST,
193 request);
194
195 HttpResponse response = null;
196 // loop until the method is successfully processed, the retryHandler
197 // returns false or a non-recoverable exception is thrown
198 for (int execCount = 0; ; execCount++) {
199 try {
200 doEstablishConnection(conn, conn.getTargetHost(),
201 request.getParams());
202 response = doSendRequest(request, conn, this.defaultContext);
203 if (response == null) {
204 response = doReceiveResponse(request, conn,
205 this.defaultContext);
206 }
207 // exit retry loop
208 break;
209 } catch (IOException ex) {
210 conn.close();
211 if (this.retryhandler == null) {
212 throw ex;
213 }
214 if (!this.retryhandler.retryRequest(ex, execCount, null)) {
215 throw ex;
216 }
217 } catch (HttpException ex) {
218 conn.close();
219 throw ex;
220 } catch (RuntimeException ex) {
221 conn.close();
222 throw ex;
223 }
224 }
225
226 doFinishResponse(response, this.defaultContext);
227
228 return response;
229
230 }
231
232 /**
233 * Prepare a request for sending.
234 *
235 * @param request the request to prepare
236 * @param context the context for sending the request
237 *
238 * @throws HttpException in case of a protocol or processing problem
239 * @throws IOException in case of an I/O problem
240 */
241 protected void doPrepareRequest(
242 final HttpRequest request,
243 final HttpContext context)
244 throws HttpException, IOException {
245 if (request == null) {
246 throw new IllegalArgumentException("HTTP request may not be null");
247 }
248 if (context == null) {
249 throw new IllegalArgumentException("HTTP context may not be null");
250 }
251 // link default parameters
252 request.getParams().setDefaults(this.params);
253 preprocessRequest(request, context);
254 }
255
256 /**
257 * Establish a connection with the target host.
258 *
259 * @param conn the HTTP connection
260 * @param target the target host for the request, or
261 * <code>null</code> to send to the host already
262 * set as the connection target
263 *
264 * @throws HttpException in case of a problem
265 * @throws IOException in case of an IO problem
266 */
267 protected void doEstablishConnection(
268 final HttpClientConnection conn,
269 final HttpHost target,
270 final HttpParams params)
271 throws HttpException, IOException {
272 if (conn == null) {
273 throw new IllegalArgumentException("HTTP connection may not be null");
274 }
275 if (target == null) {
276 throw new IllegalArgumentException("Target host may not be null");
277 }
278 if (params == null) {
279 throw new IllegalArgumentException("HTTP parameters may not be null");
280 }
281 // make sure the connection is open and points to the target host
282 if ((target == null) || target.equals(conn.getTargetHost())) {
283 // host and port ok, check whether connection needs to be opened
284 if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
285 if (conn.isOpen() && conn.isStale()) {
286 conn.close();
287 }
288 }
289 if (!conn.isOpen()) {
290 conn.open(params);
291 //TODO: Implement secure tunnelling (@@@ HttpRequestExecutor)
292 }
293
294 } else {
295 // wrong target, point connection to target
296 if (conn.isOpen()) {
297 conn.close();
298 }
299 conn.setTargetHost(target);
300 conn.open(params);
301
302 } // if connection points to target else
303 }
304
305 /**
306 * Send a request over a connection.
307 * This method also handles the expect-continue handshake if necessary.
308 * If it does not have to handle an expect-continue handshake, it will
309 * not use the connection for reading or anything else that depends on
310 * data coming in over the connection.
311 *
312 * @param request the request to send, already
313 * {@link #doPrepareRequest prepared}
314 * @param conn the connection over which to send the request, already
315 * {@link #doEstablishConnection established}
316 * @param context the context for sending the request
317 *
318 * @return a terminal response received as part of an expect-continue
319 * handshake, or
320 * <code>null</code> if the expect-continue handshake is not used
321 *
322 * @throws HttpException in case of a protocol or processing problem
323 * @throws IOException in case of an I/O problem
324 */
325 protected HttpResponse doSendRequest(
326 final HttpRequest request,
327 final HttpClientConnection conn,
328 final HttpContext context)
329 throws IOException, HttpException {
330 if (request == null) {
331 throw new IllegalArgumentException("HTTP request may not be null");
332 }
333 if (conn == null) {
334 throw new IllegalArgumentException("HTTP connection may not be null");
335 }
336 if (context == null) {
337 throw new IllegalArgumentException("HTTP context may not be null");
338 }
339
340 HttpResponse response = null;
341 context.setAttribute(HttpExecutionContext.HTTP_REQ_SENT, Boolean.FALSE);
342
343 conn.sendRequestHeader(request);
344 if (request instanceof HttpEntityEnclosingRequest) {
345 HttpEntityEnclosingRequest entityEnclRequest =
346 (HttpEntityEnclosingRequest) request;
347
348 // Check for expect-continue handshake. We have to flush the
349 // headers and wait for an 100-continue response to handle it.
350 // If we get a different response, we must not send the entity.
351 boolean sendentity = true;
352 final HttpVersion ver = request.getRequestLine().getHttpVersion();
353 if (entityEnclRequest.expectContinue() &&
354 ver.greaterEquals(HttpVersion.HTTP_1_1)) {
355
356 conn.flush();
357 // As suggested by RFC 2616 section 8.2.3, we don't wait for a
358 // 100-continue response forever. On timeout, send the entity.
359 if (conn.isResponseAvailable(WAIT_FOR_CONTINUE_MS)) {
360 response = conn.receiveResponseHeader(request.getParams());
361 if (canResponseHaveBody(request, response)) {
362 conn.receiveResponseEntity(response);
363 }
364 int status = response.getStatusLine().getStatusCode();
365 if (status < 200) {
366 //@@@ TODO: is this in line with RFC 2616, 10.1?
367 if (status != HttpStatus.SC_CONTINUE) {
368 throw new ProtocolException(
369 "Unexpected response: " + response.getStatusLine());
370 }
371 // discard 100-continue
372 response = null;
373 } else {
374 sendentity = false;
375 }
376 }
377 }
378 if (sendentity) {
379 conn.sendRequestEntity(entityEnclRequest);
380 }
381 }
382 conn.flush();
383 context.setAttribute(HttpExecutionContext.HTTP_REQ_SENT, Boolean.TRUE);
384 return response;
385 }
386
387 /**
388 * Wait for and receive a response.
389 * This method will automatically ignore intermediate responses
390 * with status code 1xx.
391 *
392 * @param request the request for which to obtain the response
393 * @param conn the connection over which the request was sent
394 * @param context the context for receiving the response
395 *
396 * @return the final response, not yet post-processed
397 *
398 * @throws HttpException in case of a protocol or processing problem
399 * @throws IOException in case of an I/O problem
400 */
401 protected HttpResponse doReceiveResponse(
402 final HttpRequest request,
403 final HttpClientConnection conn,
404 final HttpContext context)
405 throws HttpException, IOException {
406 if (request == null) {
407 throw new IllegalArgumentException("HTTP request may not be null");
408 }
409 if (conn == null) {
410 throw new IllegalArgumentException("HTTP connection may not be null");
411 }
412 if (context == null) {
413 throw new IllegalArgumentException("HTTP context may not be null");
414 }
415
416 HttpResponse response = null;
417 int statuscode = 0;
418
419 while (response == null || statuscode < HttpStatus.SC_OK) {
420
421 response = conn.receiveResponseHeader(request.getParams());
422 if (canResponseHaveBody(request, response)) {
423 conn.receiveResponseEntity(response);
424 }
425 statuscode = response.getStatusLine().getStatusCode();
426
427 } // while intermediate response
428
429 return response;
430
431 }
432
433 /**
434 * Finish a response.
435 * This includes post-processing of the response object.
436 * It does <i>not</i> read the response entity, if any.
437 * It does <i>not</i> allow for immediate re-use of the
438 * connection over which the response is coming in.
439 *
440 * @param response the response object to finish
441 * @param context the context for post-processing the response
442 *
443 * @throws HttpException in case of a protocol or processing problem
444 * @throws IOException in case of an I/O problem
445 */
446 protected void doFinishResponse(
447 final HttpResponse response,
448 final HttpContext context)
449 throws HttpException, IOException {
450 if (response == null) {
451 throw new IllegalArgumentException("HTTP response may not be null");
452 }
453 if (context == null) {
454 throw new IllegalArgumentException("HTTP context may not be null");
455 }
456 postprocessResponse(response, context);
457 }
458
459 } // class HttpRequestExecutor