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