mobile/android/thirdparty/ch/boye/httpclientandroidlib/protocol/HttpRequestExecutor.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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  */
    28 package ch.boye.httpclientandroidlib.protocol;
    30 import java.io.IOException;
    31 import java.net.ProtocolException;
    33 import ch.boye.httpclientandroidlib.HttpClientConnection;
    34 import ch.boye.httpclientandroidlib.HttpEntity;
    35 import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
    36 import ch.boye.httpclientandroidlib.HttpException;
    37 import ch.boye.httpclientandroidlib.HttpRequest;
    38 import ch.boye.httpclientandroidlib.HttpResponse;
    39 import ch.boye.httpclientandroidlib.HttpStatus;
    40 import ch.boye.httpclientandroidlib.HttpVersion;
    41 import ch.boye.httpclientandroidlib.ProtocolVersion;
    42 import ch.boye.httpclientandroidlib.params.CoreProtocolPNames;
    44 /**
    45  * HttpRequestExecutor is a client side HTTP protocol handler based on the
    46  * blocking I/O model that implements the essential requirements of the HTTP
    47  * protocol for the client side message  processing, as described by RFC 2616.
    48  * <br>
    49  * HttpRequestExecutor relies on {@link HttpProcessor} to generate mandatory
    50  * protocol headers for all outgoing messages and apply common, cross-cutting
    51  * message transformations to all incoming and outgoing messages. Application
    52  * specific processing can be implemented outside HttpRequestExecutor once the
    53  * request has been executed and a response has been received.
    54  * <p>
    55  * The following parameters can be used to customize the behavior of this
    56  * class:
    57  * <ul>
    58  *  <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li>
    59  * </ul>
    60  *
    61  * @since 4.0
    62  */
    63 public class HttpRequestExecutor {
    65     /**
    66      * Create a new request executor.
    67      */
    68     public HttpRequestExecutor() {
    69         super();
    70     }
    72     /**
    73      * Decide whether a response comes with an entity.
    74      * The implementation in this class is based on RFC 2616.
    75      * <br/>
    76      * Derived executors can override this method to handle
    77      * methods and response codes not specified in RFC 2616.
    78      *
    79      * @param request   the request, to obtain the executed method
    80      * @param response  the response, to obtain the status code
    81      */
    82     protected boolean canResponseHaveBody(final HttpRequest request,
    83                                           final HttpResponse response) {
    85         if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
    86             return false;
    87         }
    88         int status = response.getStatusLine().getStatusCode();
    89         return status >= HttpStatus.SC_OK
    90             && status != HttpStatus.SC_NO_CONTENT
    91             && status != HttpStatus.SC_NOT_MODIFIED
    92             && status != HttpStatus.SC_RESET_CONTENT;
    93     }
    95     /**
    96      * Sends the request and obtain a response.
    97      *
    98      * @param request   the request to execute.
    99      * @param conn      the connection over which to execute the request.
   100      *
   101      * @return  the response to the request.
   102      *
   103      * @throws IOException in case of an I/O error.
   104      * @throws HttpException in case of HTTP protocol violation or a processing
   105      *   problem.
   106      */
   107     public HttpResponse execute(
   108             final HttpRequest request,
   109             final HttpClientConnection conn,
   110             final HttpContext context)
   111                 throws IOException, HttpException {
   112         if (request == null) {
   113             throw new IllegalArgumentException("HTTP request may not be null");
   114         }
   115         if (conn == null) {
   116             throw new IllegalArgumentException("Client connection may not be null");
   117         }
   118         if (context == null) {
   119             throw new IllegalArgumentException("HTTP context may not be null");
   120         }
   122         try {
   123             HttpResponse response = doSendRequest(request, conn, context);
   124             if (response == null) {
   125                 response = doReceiveResponse(request, conn, context);
   126             }
   127             return response;
   128         } catch (IOException ex) {
   129             closeConnection(conn);
   130             throw ex;
   131         } catch (HttpException ex) {
   132             closeConnection(conn);
   133             throw ex;
   134         } catch (RuntimeException ex) {
   135             closeConnection(conn);
   136             throw ex;
   137         }
   138     }
   140     private final static void closeConnection(final HttpClientConnection conn) {
   141         try {
   142             conn.close();
   143         } catch (IOException ignore) {
   144         }
   145     }
   147     /**
   148      * Pre-process the given request using the given protocol processor and
   149      * initiates the process of request execution.
   150      *
   151      * @param request   the request to prepare
   152      * @param processor the processor to use
   153      * @param context   the context for sending the request
   154      *
   155      * @throws IOException in case of an I/O error.
   156      * @throws HttpException in case of HTTP protocol violation or a processing
   157      *   problem.
   158      */
   159     public void preProcess(
   160             final HttpRequest request,
   161             final HttpProcessor processor,
   162             final HttpContext context)
   163                 throws HttpException, IOException {
   164         if (request == null) {
   165             throw new IllegalArgumentException("HTTP request may not be null");
   166         }
   167         if (processor == null) {
   168             throw new IllegalArgumentException("HTTP processor may not be null");
   169         }
   170         if (context == null) {
   171             throw new IllegalArgumentException("HTTP context may not be null");
   172         }
   173         context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
   174         processor.process(request, context);
   175     }
   177     /**
   178      * Send the given request over the given connection.
   179      * <p>
   180      * This method also handles the expect-continue handshake if necessary.
   181      * If it does not have to handle an expect-continue handshake, it will
   182      * not use the connection for reading or anything else that depends on
   183      * data coming in over the connection.
   184      *
   185      * @param request   the request to send, already
   186      *                  {@link #preProcess preprocessed}
   187      * @param conn      the connection over which to send the request,
   188      *                  already established
   189      * @param context   the context for sending the request
   190      *
   191      * @return  a terminal response received as part of an expect-continue
   192      *          handshake, or
   193      *          <code>null</code> if the expect-continue handshake is not used
   194      *
   195      * @throws IOException in case of an I/O error.
   196      * @throws HttpException in case of HTTP protocol violation or a processing
   197      *   problem.
   198      */
   199     protected HttpResponse doSendRequest(
   200             final HttpRequest request,
   201             final HttpClientConnection conn,
   202             final HttpContext context)
   203                 throws IOException, HttpException {
   204         if (request == null) {
   205             throw new IllegalArgumentException("HTTP request may not be null");
   206         }
   207         if (conn == null) {
   208             throw new IllegalArgumentException("HTTP connection may not be null");
   209         }
   210         if (context == null) {
   211             throw new IllegalArgumentException("HTTP context may not be null");
   212         }
   214         HttpResponse response = null;
   216         context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
   217         context.setAttribute(ExecutionContext.HTTP_REQ_SENT, Boolean.FALSE);
   219         conn.sendRequestHeader(request);
   220         if (request instanceof HttpEntityEnclosingRequest) {
   221             // Check for expect-continue handshake. We have to flush the
   222             // headers and wait for an 100-continue response to handle it.
   223             // If we get a different response, we must not send the entity.
   224             boolean sendentity = true;
   225             final ProtocolVersion ver =
   226                 request.getRequestLine().getProtocolVersion();
   227             if (((HttpEntityEnclosingRequest) request).expectContinue() &&
   228                 !ver.lessEquals(HttpVersion.HTTP_1_0)) {
   230                 conn.flush();
   231                 // As suggested by RFC 2616 section 8.2.3, we don't wait for a
   232                 // 100-continue response forever. On timeout, send the entity.
   233                 int tms = request.getParams().getIntParameter(
   234                         CoreProtocolPNames.WAIT_FOR_CONTINUE, 2000);
   236                 if (conn.isResponseAvailable(tms)) {
   237                     response = conn.receiveResponseHeader();
   238                     if (canResponseHaveBody(request, response)) {
   239                         conn.receiveResponseEntity(response);
   240                     }
   241                     int status = response.getStatusLine().getStatusCode();
   242                     if (status < 200) {
   243                         if (status != HttpStatus.SC_CONTINUE) {
   244                             throw new ProtocolException(
   245                                     "Unexpected response: " + response.getStatusLine());
   246                         }
   247                         // discard 100-continue
   248                         response = null;
   249                     } else {
   250                         sendentity = false;
   251                     }
   252                 }
   253             }
   254             if (sendentity) {
   255                 conn.sendRequestEntity((HttpEntityEnclosingRequest) request);
   256             }
   257         }
   258         conn.flush();
   259         context.setAttribute(ExecutionContext.HTTP_REQ_SENT, Boolean.TRUE);
   260         return response;
   261     }
   263     /**
   264      * Waits for and receives a response.
   265      * This method will automatically ignore intermediate responses
   266      * with status code 1xx.
   267      *
   268      * @param request   the request for which to obtain the response
   269      * @param conn      the connection over which the request was sent
   270      * @param context   the context for receiving the response
   271      *
   272      * @return  the terminal response, not yet post-processed
   273      *
   274      * @throws IOException in case of an I/O error.
   275      * @throws HttpException in case of HTTP protocol violation or a processing
   276      *   problem.
   277      */
   278     protected HttpResponse doReceiveResponse(
   279             final HttpRequest          request,
   280             final HttpClientConnection conn,
   281             final HttpContext          context)
   282                 throws HttpException, IOException {
   283         if (request == null) {
   284             throw new IllegalArgumentException("HTTP request may not be null");
   285         }
   286         if (conn == null) {
   287             throw new IllegalArgumentException("HTTP connection may not be null");
   288         }
   289         if (context == null) {
   290             throw new IllegalArgumentException("HTTP context may not be null");
   291         }
   293         HttpResponse response = null;
   294         int statuscode = 0;
   296         while (response == null || statuscode < HttpStatus.SC_OK) {
   298             response = conn.receiveResponseHeader();
   299             if (canResponseHaveBody(request, response)) {
   300                 conn.receiveResponseEntity(response);
   301             }
   302             statuscode = response.getStatusLine().getStatusCode();
   304         } // while intermediate response
   306         return response;
   308     }
   310     /**
   311      * Post-processes the given response using the given protocol processor and
   312      * completes the process of request execution.
   313      * <p>
   314      * This method does <i>not</i> read the response entity, if any.
   315      * The connection over which content of the response entity is being
   316      * streamed from cannot be reused until {@link HttpEntity#consumeContent()}
   317      * has been invoked.
   318      *
   319      * @param response  the response object to post-process
   320      * @param processor the processor to use
   321      * @param context   the context for post-processing the response
   322      *
   323      * @throws IOException in case of an I/O error.
   324      * @throws HttpException in case of HTTP protocol violation or a processing
   325      *   problem.
   326      */
   327     public void postProcess(
   328             final HttpResponse response,
   329             final HttpProcessor processor,
   330             final HttpContext context)
   331                 throws HttpException, IOException {
   332         if (response == null) {
   333             throw new IllegalArgumentException("HTTP response may not be null");
   334         }
   335         if (processor == null) {
   336             throw new IllegalArgumentException("HTTP processor may not be null");
   337         }
   338         if (context == null) {
   339             throw new IllegalArgumentException("HTTP context may not be null");
   340         }
   341         context.setAttribute(ExecutionContext.HTTP_RESPONSE, response);
   342         processor.process(response, context);
   343     }
   345 } // class HttpRequestExecutor

mercurial