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.util.Args;
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 *
56 * @since 4.0
57 */
58 @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 * <br/>
83 * Derived executors can override this method to handle
84 * methods and response codes not specified in RFC 2616.
85 *
86 * @param request the request, to obtain the executed method
87 * @param response the response, to obtain the status code
88 */
89 protected boolean canResponseHaveBody(final HttpRequest request,
90 final HttpResponse response) {
91
92 if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
93 return false;
94 }
95 final int status = response.getStatusLine().getStatusCode();
96 return status >= HttpStatus.SC_OK
97 && status != HttpStatus.SC_NO_CONTENT
98 && status != HttpStatus.SC_NOT_MODIFIED
99 && status != HttpStatus.SC_RESET_CONTENT;
100 }
101
102 /**
103 * Sends the request and obtain a response.
104 *
105 * @param request the request to execute.
106 * @param conn the connection over which to execute the request.
107 *
108 * @return the response to the request.
109 *
110 * @throws IOException in case of an I/O error.
111 * @throws HttpException in case of HTTP protocol violation or a processing
112 * problem.
113 */
114 public HttpResponse execute(
115 final HttpRequest request,
116 final HttpClientConnection conn,
117 final HttpContext context) throws IOException, HttpException {
118 Args.notNull(request, "HTTP request");
119 Args.notNull(conn, "Client connection");
120 Args.notNull(context, "HTTP context");
121 try {
122 HttpResponse response = doSendRequest(request, conn, context);
123 if (response == null) {
124 response = doReceiveResponse(request, conn, context);
125 }
126 return response;
127 } catch (final IOException ex) {
128 closeConnection(conn);
129 throw ex;
130 } catch (final HttpException ex) {
131 closeConnection(conn);
132 throw ex;
133 } catch (final RuntimeException ex) {
134 closeConnection(conn);
135 throw ex;
136 }
137 }
138
139 private final static void closeConnection(final HttpClientConnection conn) {
140 try {
141 conn.close();
142 } catch (final IOException ignore) {
143 }
144 }
145
146 /**
147 * Pre-process the given request using the given protocol processor and
148 * initiates the process of request execution.
149 *
150 * @param request the request to prepare
151 * @param processor the processor to use
152 * @param context the context for sending the request
153 *
154 * @throws IOException in case of an I/O error.
155 * @throws HttpException in case of HTTP protocol violation or a processing
156 * problem.
157 */
158 public void preProcess(
159 final HttpRequest request,
160 final HttpProcessor processor,
161 final HttpContext context) throws HttpException, IOException {
162 Args.notNull(request, "HTTP request");
163 Args.notNull(processor, "HTTP processor");
164 Args.notNull(context, "HTTP context");
165 context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
166 processor.process(request, context);
167 }
168
169 /**
170 * Send the given request over the given connection.
171 * <p>
172 * This method also handles the expect-continue handshake if necessary.
173 * If it does not have to handle an expect-continue handshake, it will
174 * not use the connection for reading or anything else that depends on
175 * data coming in over the connection.
176 *
177 * @param request the request to send, already
178 * {@link #preProcess preprocessed}
179 * @param conn the connection over which to send the request,
180 * already established
181 * @param context the context for sending the request
182 *
183 * @return a terminal response received as part of an expect-continue
184 * handshake, or
185 * <code>null</code> if the expect-continue handshake is not used
186 *
187 * @throws IOException in case of an I/O error.
188 * @throws HttpException in case of HTTP protocol violation or a processing
189 * problem.
190 */
191 protected HttpResponse doSendRequest(
192 final HttpRequest request,
193 final HttpClientConnection conn,
194 final HttpContext context) throws IOException, HttpException {
195 Args.notNull(request, "HTTP request");
196 Args.notNull(conn, "Client connection");
197 Args.notNull(context, "HTTP context");
198
199 HttpResponse response = null;
200
201 context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
202 context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.FALSE);
203
204 conn.sendRequestHeader(request);
205 if (request instanceof HttpEntityEnclosingRequest) {
206 // Check for expect-continue handshake. We have to flush the
207 // headers and wait for an 100-continue response to handle it.
208 // If we get a different response, we must not send the entity.
209 boolean sendentity = true;
210 final ProtocolVersion ver =
211 request.getRequestLine().getProtocolVersion();
212 if (((HttpEntityEnclosingRequest) request).expectContinue() &&
213 !ver.lessEquals(HttpVersion.HTTP_1_0)) {
214
215 conn.flush();
216 // As suggested by RFC 2616 section 8.2.3, we don't wait for a
217 // 100-continue response forever. On timeout, send the entity.
218 if (conn.isResponseAvailable(this.waitForContinue)) {
219 response = conn.receiveResponseHeader();
220 if (canResponseHaveBody(request, response)) {
221 conn.receiveResponseEntity(response);
222 }
223 final 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(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE);
242 return response;
243 }
244
245 /**
246 * Waits for and receives 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 terminal response, not yet post-processed
255 *
256 * @throws IOException in case of an I/O error.
257 * @throws HttpException in case of HTTP protocol violation or a processing
258 * problem.
259 */
260 protected HttpResponse doReceiveResponse(
261 final HttpRequest request,
262 final HttpClientConnection conn,
263 final HttpContext context) throws HttpException, IOException {
264 Args.notNull(request, "HTTP request");
265 Args.notNull(conn, "Client connection");
266 Args.notNull(context, "HTTP context");
267 HttpResponse response = null;
268 int statusCode = 0;
269
270 while (response == null || statusCode < HttpStatus.SC_OK) {
271
272 response = conn.receiveResponseHeader();
273 if (canResponseHaveBody(request, response)) {
274 conn.receiveResponseEntity(response);
275 }
276 statusCode = response.getStatusLine().getStatusCode();
277
278 } // while intermediate response
279
280 return response;
281 }
282
283 /**
284 * Post-processes the given response using the given protocol processor and
285 * completes the process of request execution.
286 * <p>
287 * This method does <i>not</i> read the response entity, if any.
288 * The connection over which content of the response entity is being
289 * streamed from cannot be reused until
290 * {@link org.apache.http.util.EntityUtils#consume(HttpEntity)}
291 * has been invoked.
292 *
293 * @param response the response object to post-process
294 * @param processor the processor to use
295 * @param context the context for post-processing the response
296 *
297 * @throws IOException in case of an I/O error.
298 * @throws HttpException in case of HTTP protocol violation or a processing
299 * problem.
300 */
301 public void postProcess(
302 final HttpResponse response,
303 final HttpProcessor processor,
304 final HttpContext context) throws HttpException, IOException {
305 Args.notNull(response, "HTTP response");
306 Args.notNull(processor, "HTTP processor");
307 Args.notNull(context, "HTTP context");
308 context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
309 processor.process(response, context);
310 }
311
312 } // class HttpRequestExecutor