Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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