Home | History | Annotate | Download | only in protocol
      1 /*
      2  * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/protocol/HttpRequestExecutor.java $
      3  * $Revision: 576073 $
      4  * $Date: 2007-09-16 03:53:13 -0700 (Sun, 16 Sep 2007) $
      5  *
      6  * ====================================================================
      7  * Licensed to the Apache Software Foundation (ASF) under one
      8  * or more contributor license agreements.  See the NOTICE file
      9  * distributed with this work for additional information
     10  * regarding copyright ownership.  The ASF licenses this file
     11  * to you under the Apache License, Version 2.0 (the
     12  * "License"); you may not use this file except in compliance
     13  * with the License.  You may obtain a copy of the License at
     14  *
     15  *   http://www.apache.org/licenses/LICENSE-2.0
     16  *
     17  * Unless required by applicable law or agreed to in writing,
     18  * software distributed under the License is distributed on an
     19  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     20  * KIND, either express or implied.  See the License for the
     21  * specific language governing permissions and limitations
     22  * under the License.
     23  * ====================================================================
     24  *
     25  * This software consists of voluntary contributions made by many
     26  * individuals on behalf of the Apache Software Foundation.  For more
     27  * information on the Apache Software Foundation, please see
     28  * <http://www.apache.org/>.
     29  *
     30  */
     31 
     32 package org.apache.http.protocol;
     33 
     34 import java.io.IOException;
     35 import java.net.ProtocolException;
     36 
     37 import org.apache.http.HttpClientConnection;
     38 import org.apache.http.HttpEntityEnclosingRequest;
     39 import org.apache.http.HttpException;
     40 import org.apache.http.HttpRequest;
     41 import org.apache.http.HttpResponse;
     42 import org.apache.http.HttpStatus;
     43 import org.apache.http.HttpVersion;
     44 import org.apache.http.ProtocolVersion;
     45 import org.apache.http.params.CoreProtocolPNames;
     46 
     47 /**
     48  * Sends HTTP requests and receives the responses.
     49  * Takes care of request preprocessing and response postprocessing
     50  * by the respective interceptors.
     51  *
     52  * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
     53  *
     54  * @version $Revision: 576073 $
     55  *
     56  * @since 4.0
     57  */
     58 public class HttpRequestExecutor {
     59 
     60     /**
     61      * Create a new request executor.
     62      */
     63     public HttpRequestExecutor() {
     64         super();
     65     }
     66 
     67     /**
     68      * Decide whether a response comes with an entity.
     69      * The implementation in this class is based on RFC 2616.
     70      * Unknown methods and response codes are supposed to
     71      * indicate responses with an entity.
     72      * <br/>
     73      * Derived executors can override this method to handle
     74      * methods and response codes not specified in RFC 2616.
     75      *
     76      * @param request   the request, to obtain the executed method
     77      * @param response  the response, to obtain the status code
     78      */
     79     protected boolean canResponseHaveBody(final HttpRequest request,
     80                                           final HttpResponse response) {
     81 
     82         if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
     83             return false;
     84         }
     85         int status = response.getStatusLine().getStatusCode();
     86         return status >= HttpStatus.SC_OK
     87             && status != HttpStatus.SC_NO_CONTENT
     88             && status != HttpStatus.SC_NOT_MODIFIED
     89             && status != HttpStatus.SC_RESET_CONTENT;
     90     }
     91 
     92     /**
     93      * Synchronously send a request and obtain the response.
     94      *
     95      * @param request   the request to send. It will be preprocessed.
     96      * @param conn      the open connection over which to send
     97      *
     98      * @return  the response to the request, postprocessed
     99      *
    100      * @throws HttpException      in case of a protocol or processing problem
    101      * @throws IOException        in case of an I/O problem
    102      */
    103     public HttpResponse execute(
    104             final HttpRequest request,
    105             final HttpClientConnection conn,
    106             final HttpContext context)
    107                 throws IOException, HttpException {
    108         if (request == null) {
    109             throw new IllegalArgumentException("HTTP request may not be null");
    110         }
    111         if (conn == null) {
    112             throw new IllegalArgumentException("Client connection may not be null");
    113         }
    114         if (context == null) {
    115             throw new IllegalArgumentException("HTTP context may not be null");
    116         }
    117 
    118         try {
    119             HttpResponse response = doSendRequest(request, conn, context);
    120             if (response == null) {
    121                 response = doReceiveResponse(request, conn, context);
    122             }
    123             return response;
    124         } catch (IOException ex) {
    125             conn.close();
    126             throw ex;
    127         } catch (HttpException ex) {
    128             conn.close();
    129             throw ex;
    130         } catch (RuntimeException ex) {
    131             conn.close();
    132             throw ex;
    133         }
    134     }
    135 
    136     /**
    137      * Prepare a request for sending.
    138      *
    139      * @param request   the request to prepare
    140      * @param processor the processor to use
    141      * @param context   the context for sending the request
    142      *
    143      * @throws HttpException      in case of a protocol or processing problem
    144      * @throws IOException        in case of an I/O problem
    145      */
    146     public void preProcess(
    147             final HttpRequest request,
    148             final HttpProcessor processor,
    149             final HttpContext context)
    150                 throws HttpException, IOException {
    151         if (request == null) {
    152             throw new IllegalArgumentException("HTTP request may not be null");
    153         }
    154         if (processor == null) {
    155             throw new IllegalArgumentException("HTTP processor may not be null");
    156         }
    157         if (context == null) {
    158             throw new IllegalArgumentException("HTTP context may not be null");
    159         }
    160         processor.process(request, context);
    161     }
    162 
    163     /**
    164      * Send a request over a connection.
    165      * This method also handles the expect-continue handshake if necessary.
    166      * If it does not have to handle an expect-continue handshake, it will
    167      * not use the connection for reading or anything else that depends on
    168      * data coming in over the connection.
    169      *
    170      * @param request   the request to send, already
    171      *                  {@link #preProcess preprocessed}
    172      * @param conn      the connection over which to send the request,
    173      *                  already established
    174      * @param context   the context for sending the request
    175      *
    176      * @return  a terminal response received as part of an expect-continue
    177      *          handshake, or
    178      *          <code>null</code> if the expect-continue handshake is not used
    179      *
    180      * @throws HttpException      in case of a protocol or processing problem
    181      * @throws IOException        in case of an I/O problem
    182      */
    183     protected HttpResponse doSendRequest(
    184             final HttpRequest request,
    185             final HttpClientConnection conn,
    186             final HttpContext context)
    187                 throws IOException, HttpException {
    188         if (request == null) {
    189             throw new IllegalArgumentException("HTTP request may not be null");
    190         }
    191         if (conn == null) {
    192             throw new IllegalArgumentException("HTTP connection may not be null");
    193         }
    194         if (context == null) {
    195             throw new IllegalArgumentException("HTTP context may not be null");
    196         }
    197 
    198         HttpResponse response = null;
    199         context.setAttribute(ExecutionContext.HTTP_REQ_SENT, Boolean.FALSE);
    200 
    201         conn.sendRequestHeader(request);
    202         if (request instanceof HttpEntityEnclosingRequest) {
    203             // Check for expect-continue handshake. We have to flush the
    204             // headers and wait for an 100-continue response to handle it.
    205             // If we get a different response, we must not send the entity.
    206             boolean sendentity = true;
    207             final ProtocolVersion ver =
    208                 request.getRequestLine().getProtocolVersion();
    209             if (((HttpEntityEnclosingRequest) request).expectContinue() &&
    210                 !ver.lessEquals(HttpVersion.HTTP_1_0)) {
    211 
    212                 conn.flush();
    213                 // As suggested by RFC 2616 section 8.2.3, we don't wait for a
    214                 // 100-continue response forever. On timeout, send the entity.
    215                 int tms = request.getParams().getIntParameter(
    216                         CoreProtocolPNames.WAIT_FOR_CONTINUE, 2000);
    217 
    218                 if (conn.isResponseAvailable(tms)) {
    219                     response = conn.receiveResponseHeader();
    220                     if (canResponseHaveBody(request, response)) {
    221                         conn.receiveResponseEntity(response);
    222                     }
    223                     int status = response.getStatusLine().getStatusCode();
    224                     if (status < 200) {
    225                         if (status != HttpStatus.SC_CONTINUE) {
    226                             throw new ProtocolException(
    227                                     "Unexpected response: " + response.getStatusLine());
    228                         }
    229                         // discard 100-continue
    230                         response = null;
    231                     } else {
    232                         sendentity = false;
    233                     }
    234                 }
    235             }
    236             if (sendentity) {
    237                 conn.sendRequestEntity((HttpEntityEnclosingRequest) request);
    238             }
    239         }
    240         conn.flush();
    241         context.setAttribute(ExecutionContext.HTTP_REQ_SENT, Boolean.TRUE);
    242         return response;
    243     }
    244 
    245     /**
    246      * Wait for and receive a response.
    247      * This method will automatically ignore intermediate responses
    248      * with status code 1xx.
    249      *
    250      * @param request   the request for which to obtain the response
    251      * @param conn      the connection over which the request was sent
    252      * @param context   the context for receiving the response
    253      *
    254      * @return  the final response, not yet post-processed
    255      *
    256      * @throws HttpException      in case of a protocol or processing problem
    257      * @throws IOException        in case of an I/O problem
    258      */
    259     protected HttpResponse doReceiveResponse(
    260             final HttpRequest          request,
    261             final HttpClientConnection conn,
    262             final HttpContext          context)
    263                 throws HttpException, IOException {
    264         if (request == null) {
    265             throw new IllegalArgumentException("HTTP request may not be null");
    266         }
    267         if (conn == null) {
    268             throw new IllegalArgumentException("HTTP connection may not be null");
    269         }
    270         if (context == null) {
    271             throw new IllegalArgumentException("HTTP context may not be null");
    272         }
    273 
    274         HttpResponse response = null;
    275         int statuscode = 0;
    276 
    277         while (response == null || statuscode < HttpStatus.SC_OK) {
    278 
    279             response = conn.receiveResponseHeader();
    280             if (canResponseHaveBody(request, response)) {
    281                 conn.receiveResponseEntity(response);
    282             }
    283             statuscode = response.getStatusLine().getStatusCode();
    284 
    285         } // while intermediate response
    286 
    287         return response;
    288 
    289     }
    290 
    291     /**
    292      * Finish a response.
    293      * This includes post-processing of the response object.
    294      * It does <i>not</i> read the response entity, if any.
    295      * It does <i>not</i> allow for immediate re-use of the
    296      * connection over which the response is coming in.
    297      *
    298      * @param response  the response object to finish
    299      * @param processor the processor to use
    300      * @param context   the context for post-processing the response
    301      *
    302      * @throws HttpException      in case of a protocol or processing problem
    303      * @throws IOException        in case of an I/O problem
    304      */
    305     public void postProcess(
    306             final HttpResponse response,
    307             final HttpProcessor processor,
    308             final HttpContext context)
    309                 throws HttpException, IOException {
    310         if (response == null) {
    311             throw new IllegalArgumentException("HTTP response may not be null");
    312         }
    313         if (processor == null) {
    314             throw new IllegalArgumentException("HTTP processor may not be null");
    315         }
    316         if (context == null) {
    317             throw new IllegalArgumentException("HTTP context may not be null");
    318         }
    319         processor.process(response, context);
    320     }
    321 
    322 } // class HttpRequestExecutor
    323