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 32package org.apache.http.protocol; 33 34import java.io.IOException; 35import java.net.ProtocolException; 36 37import org.apache.http.HttpClientConnection; 38import org.apache.http.HttpEntityEnclosingRequest; 39import org.apache.http.HttpException; 40import org.apache.http.HttpRequest; 41import org.apache.http.HttpResponse; 42import org.apache.http.HttpStatus; 43import org.apache.http.HttpVersion; 44import org.apache.http.ProtocolVersion; 45import 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 */ 58public 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