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