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.HttpEntityEnclosingRequest;
34 import org.apache.http.HttpException;
35 import org.apache.http.HttpRequest;
36 import org.apache.http.HttpResponse;
37 import org.apache.http.HttpStatus;
38 import org.apache.http.HttpVersion;
39 import org.apache.http.ProtocolException;
40 import org.apache.http.ProtocolVersion;
41 import org.apache.http.annotation.Contract;
42 import org.apache.http.annotation.ThreadingBehavior;
43 import org.apache.http.util.Args;
44
45 /**
46 * {@code HttpRequestExecutor} is a client side HTTP protocol handler based
47 * on the blocking (classic) I/O model.
48 * <p>
49 * {@code HttpRequestExecutor} 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 * {@code HttpRequestExecutor} once the request has been executed and
54 * a response has been received.
55 *
56 * @since 4.0
57 */
58 @Contract(threading = ThreadingBehavior.IMMUTABLE)
59 public class HttpRequestExecutor {
60
61 public static final int DEFAULT_WAIT_FOR_CONTINUE = 3000;
62
63 private final int waitForContinue;
64
65 /**
66 * Creates new instance of HttpRequestExecutor.
67 *
68 * @since 4.3
69 */
70 public HttpRequestExecutor(final int waitForContinue) {
71 super();
72 this.waitForContinue = Args.positive(waitForContinue, "Wait for continue time");
73 }
74
75 public HttpRequestExecutor() {
76 this(DEFAULT_WAIT_FOR_CONTINUE);
77 }
78
79 /**
80 * Decide whether a response comes with an entity.
81 * The implementation in this class is based on RFC 2616.
82 * <p>
83 * Derived executors can override this method to handle
84 * methods and response codes not specified in RFC 2616.
85 * </p>
86 *
87 * @param request the request, to obtain the executed method
88 * @param response the response, to obtain the status code
89 */
90 protected boolean canResponseHaveBody(final HttpRequest request,
91 final HttpResponse response) {
92
93 if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
94 return false;
95 }
96 final int status = response.getStatusLine().getStatusCode();
97 return status >= HttpStatus.SC_OK
98 && status != HttpStatus.SC_NO_CONTENT
99 && 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 statusCode = response.getStatusLine().getStatusCode();
275 if (statusCode < HttpStatus.SC_CONTINUE) {
276 throw new ProtocolException("Invalid response: " + response.getStatusLine());
277 }
278 if (canResponseHaveBody(request, response)) {
279 conn.receiveResponseEntity(response);
280 }
281
282 } // while intermediate response
283
284 return response;
285 }
286
287 /**
288 * Post-processes the given response using the given protocol processor and
289 * completes the process of request execution.
290 * <p>
291 * This method does <i>not</i> read the response entity, if any.
292 * The connection over which content of the response entity is being
293 * streamed from cannot be reused until
294 * {@link org.apache.http.util.EntityUtils#consume(org.apache.http.HttpEntity)}
295 * has been invoked.
296 *
297 * @param response the response object to post-process
298 * @param processor the processor to use
299 * @param context the context for post-processing the response
300 *
301 * @throws IOException in case of an I/O error.
302 * @throws HttpException in case of HTTP protocol violation or a processing
303 * problem.
304 */
305 public void postProcess(
306 final HttpResponse response,
307 final HttpProcessor processor,
308 final HttpContext context) throws HttpException, IOException {
309 Args.notNull(response, "HTTP response");
310 Args.notNull(processor, "HTTP processor");
311 Args.notNull(context, "HTTP context");
312 context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
313 processor.process(response, context);
314 }
315
316 } // class HttpRequestExecutor
317