001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.protocol; 029 030import java.io.IOException; 031 032import org.apache.http.HttpClientConnection; 033import org.apache.http.HttpEntityEnclosingRequest; 034import org.apache.http.HttpException; 035import org.apache.http.HttpRequest; 036import org.apache.http.HttpResponse; 037import org.apache.http.HttpStatus; 038import org.apache.http.HttpVersion; 039import org.apache.http.ProtocolException; 040import org.apache.http.ProtocolVersion; 041import org.apache.http.annotation.ThreadingBehavior; 042import org.apache.http.annotation.Contract; 043import org.apache.http.util.Args; 044 045/** 046 * {@code HttpRequestExecutor} is a client side HTTP protocol handler based 047 * on the blocking (classic) I/O model. 048 * <p> 049 * {@code HttpRequestExecutor} relies on {@link HttpProcessor} to generate 050 * mandatory protocol headers for all outgoing messages and apply common, 051 * cross-cutting message transformations to all incoming and outgoing messages. 052 * Application specific processing can be implemented outside 053 * {@code HttpRequestExecutor} once the request has been executed and 054 * a response has been received. 055 * 056 * @since 4.0 057 */ 058@Contract(threading = ThreadingBehavior.IMMUTABLE) 059public class HttpRequestExecutor { 060 061 public static final int DEFAULT_WAIT_FOR_CONTINUE = 3000; 062 063 private final int waitForContinue; 064 065 /** 066 * Creates new instance of HttpRequestExecutor. 067 * 068 * @since 4.3 069 */ 070 public HttpRequestExecutor(final int waitForContinue) { 071 super(); 072 this.waitForContinue = Args.positive(waitForContinue, "Wait for continue time"); 073 } 074 075 public HttpRequestExecutor() { 076 this(DEFAULT_WAIT_FOR_CONTINUE); 077 } 078 079 /** 080 * Decide whether a response comes with an entity. 081 * The implementation in this class is based on RFC 2616. 082 * <p> 083 * Derived executors can override this method to handle 084 * methods and response codes not specified in RFC 2616. 085 * </p> 086 * 087 * @param request the request, to obtain the executed method 088 * @param response the response, to obtain the status code 089 */ 090 protected boolean canResponseHaveBody(final HttpRequest request, 091 final HttpResponse response) { 092 093 if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) { 094 return false; 095 } 096 final int status = response.getStatusLine().getStatusCode(); 097 return status >= HttpStatus.SC_OK 098 && status != HttpStatus.SC_NO_CONTENT 099 && status != HttpStatus.SC_NOT_MODIFIED 100 && status != HttpStatus.SC_RESET_CONTENT; 101 } 102 103 /** 104 * Sends the request and obtain a response. 105 * 106 * @param request the request to execute. 107 * @param conn the connection over which to execute the request. 108 * 109 * @return the response to the request. 110 * 111 * @throws IOException in case of an I/O error. 112 * @throws HttpException in case of HTTP protocol violation or a processing 113 * problem. 114 */ 115 public HttpResponse execute( 116 final HttpRequest request, 117 final HttpClientConnection conn, 118 final HttpContext context) throws IOException, HttpException { 119 Args.notNull(request, "HTTP request"); 120 Args.notNull(conn, "Client connection"); 121 Args.notNull(context, "HTTP context"); 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 (final IOException ex) { 129 closeConnection(conn); 130 throw ex; 131 } catch (final HttpException ex) { 132 closeConnection(conn); 133 throw ex; 134 } catch (final RuntimeException ex) { 135 closeConnection(conn); 136 throw ex; 137 } 138 } 139 140 private static void closeConnection(final HttpClientConnection conn) { 141 try { 142 conn.close(); 143 } catch (final IOException ignore) { 144 } 145 } 146 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) throws HttpException, IOException { 163 Args.notNull(request, "HTTP request"); 164 Args.notNull(processor, "HTTP processor"); 165 Args.notNull(context, "HTTP context"); 166 context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); 167 processor.process(request, context); 168 } 169 170 /** 171 * Send the given request over the given connection. 172 * <p> 173 * This method also handles the expect-continue handshake if necessary. 174 * If it does not have to handle an expect-continue handshake, it will 175 * not use the connection for reading or anything else that depends on 176 * data coming in over the connection. 177 * 178 * @param request the request to send, already 179 * {@link #preProcess preprocessed} 180 * @param conn the connection over which to send the request, 181 * already established 182 * @param context the context for sending the request 183 * 184 * @return a terminal response received as part of an expect-continue 185 * handshake, or 186 * {@code null} if the expect-continue handshake is not used 187 * 188 * @throws IOException in case of an I/O error. 189 * @throws HttpException in case of HTTP protocol violation or a processing 190 * problem. 191 */ 192 protected HttpResponse doSendRequest( 193 final HttpRequest request, 194 final HttpClientConnection conn, 195 final HttpContext context) throws IOException, HttpException { 196 Args.notNull(request, "HTTP request"); 197 Args.notNull(conn, "Client connection"); 198 Args.notNull(context, "HTTP context"); 199 200 HttpResponse response = null; 201 202 context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn); 203 context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.FALSE); 204 205 conn.sendRequestHeader(request); 206 if (request instanceof HttpEntityEnclosingRequest) { 207 // Check for expect-continue handshake. We have to flush the 208 // headers and wait for an 100-continue response to handle it. 209 // If we get a different response, we must not send the entity. 210 boolean sendentity = true; 211 final ProtocolVersion ver = 212 request.getRequestLine().getProtocolVersion(); 213 if (((HttpEntityEnclosingRequest) request).expectContinue() && 214 !ver.lessEquals(HttpVersion.HTTP_1_0)) { 215 216 conn.flush(); 217 // As suggested by RFC 2616 section 8.2.3, we don't wait for a 218 // 100-continue response forever. On timeout, send the entity. 219 if (conn.isResponseAvailable(this.waitForContinue)) { 220 response = conn.receiveResponseHeader(); 221 if (canResponseHaveBody(request, response)) { 222 conn.receiveResponseEntity(response); 223 } 224 final int status = response.getStatusLine().getStatusCode(); 225 if (status < 200) { 226 if (status != HttpStatus.SC_CONTINUE) { 227 throw new ProtocolException( 228 "Unexpected response: " + response.getStatusLine()); 229 } 230 // discard 100-continue 231 response = null; 232 } else { 233 sendentity = false; 234 } 235 } 236 } 237 if (sendentity) { 238 conn.sendRequestEntity((HttpEntityEnclosingRequest) request); 239 } 240 } 241 conn.flush(); 242 context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE); 243 return response; 244 } 245 246 /** 247 * Waits for and receives a response. 248 * This method will automatically ignore intermediate responses 249 * with status code 1xx. 250 * 251 * @param request the request for which to obtain the response 252 * @param conn the connection over which the request was sent 253 * @param context the context for receiving the response 254 * 255 * @return the terminal response, not yet post-processed 256 * 257 * @throws IOException in case of an I/O error. 258 * @throws HttpException in case of HTTP protocol violation or a processing 259 * problem. 260 */ 261 protected HttpResponse doReceiveResponse( 262 final HttpRequest request, 263 final HttpClientConnection conn, 264 final HttpContext context) throws HttpException, IOException { 265 Args.notNull(request, "HTTP request"); 266 Args.notNull(conn, "Client connection"); 267 Args.notNull(context, "HTTP context"); 268 HttpResponse response = null; 269 int statusCode = 0; 270 271 while (response == null || statusCode < HttpStatus.SC_OK) { 272 273 response = conn.receiveResponseHeader(); 274 if (canResponseHaveBody(request, response)) { 275 conn.receiveResponseEntity(response); 276 } 277 statusCode = response.getStatusLine().getStatusCode(); 278 279 } // while intermediate response 280 281 return response; 282 } 283 284 /** 285 * Post-processes the given response using the given protocol processor and 286 * completes the process of request execution. 287 * <p> 288 * This method does <i>not</i> read the response entity, if any. 289 * The connection over which content of the response entity is being 290 * streamed from cannot be reused until 291 * {@link org.apache.http.util.EntityUtils#consume(org.apache.http.HttpEntity)} 292 * has been invoked. 293 * 294 * @param response the response object to post-process 295 * @param processor the processor to use 296 * @param context the context for post-processing the response 297 * 298 * @throws IOException in case of an I/O error. 299 * @throws HttpException in case of HTTP protocol violation or a processing 300 * problem. 301 */ 302 public void postProcess( 303 final HttpResponse response, 304 final HttpProcessor processor, 305 final HttpContext context) throws HttpException, IOException { 306 Args.notNull(response, "HTTP response"); 307 Args.notNull(processor, "HTTP processor"); 308 Args.notNull(context, "HTTP context"); 309 context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); 310 processor.process(response, context); 311 } 312 313} // class HttpRequestExecutor