001: /*
002: * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/httpcore/tags/4.0-beta1/module-main/src/main/java/org/apache/http/protocol/HttpRequestExecutor.java $
003: * $Revision: 576073 $
004: * $Date: 2007-09-16 12:53:13 +0200 (Sun, 16 Sep 2007) $
005: *
006: * ====================================================================
007: * Licensed to the Apache Software Foundation (ASF) under one
008: * or more contributor license agreements. See the NOTICE file
009: * distributed with this work for additional information
010: * regarding copyright ownership. The ASF licenses this file
011: * to you under the Apache License, Version 2.0 (the
012: * "License"); you may not use this file except in compliance
013: * with the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing,
018: * software distributed under the License is distributed on an
019: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020: * KIND, either express or implied. See the License for the
021: * specific language governing permissions and limitations
022: * under the License.
023: * ====================================================================
024: *
025: * This software consists of voluntary contributions made by many
026: * individuals on behalf of the Apache Software Foundation. For more
027: * information on the Apache Software Foundation, please see
028: * <http://www.apache.org/>.
029: *
030: */
031:
032: package org.apache.http.protocol;
033:
034: import java.io.IOException;
035: import java.net.ProtocolException;
036:
037: import org.apache.http.HttpClientConnection;
038: import org.apache.http.HttpEntityEnclosingRequest;
039: import org.apache.http.HttpException;
040: import org.apache.http.HttpRequest;
041: import org.apache.http.HttpResponse;
042: import org.apache.http.HttpStatus;
043: import org.apache.http.HttpVersion;
044: import org.apache.http.ProtocolVersion;
045: import org.apache.http.params.CoreProtocolPNames;
046:
047: /**
048: * Sends HTTP requests and receives the responses.
049: * Takes care of request preprocessing and response postprocessing
050: * by the respective interceptors.
051: *
052: * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
053: *
054: * @version $Revision: 576073 $
055: *
056: * @since 4.0
057: */
058: public class HttpRequestExecutor {
059:
060: /**
061: * Create a new request executor.
062: */
063: public HttpRequestExecutor() {
064: super ();
065: }
066:
067: /**
068: * Decide whether a response comes with an entity.
069: * The implementation in this class is based on RFC 2616.
070: * Unknown methods and response codes are supposed to
071: * indicate responses with an entity.
072: * <br/>
073: * Derived executors can override this method to handle
074: * methods and response codes not specified in RFC 2616.
075: *
076: * @param request the request, to obtain the executed method
077: * @param response the response, to obtain the status code
078: */
079: protected boolean canResponseHaveBody(final HttpRequest request,
080: final HttpResponse response) {
081:
082: if ("HEAD".equalsIgnoreCase(request.getRequestLine()
083: .getMethod())) {
084: return false;
085: }
086: int status = response.getStatusLine().getStatusCode();
087: return status >= HttpStatus.SC_OK
088: && status != HttpStatus.SC_NO_CONTENT
089: && status != HttpStatus.SC_NOT_MODIFIED
090: && status != HttpStatus.SC_RESET_CONTENT;
091: }
092:
093: /**
094: * Synchronously send a request and obtain the response.
095: *
096: * @param request the request to send. It will be preprocessed.
097: * @param conn the open connection over which to send
098: *
099: * @return the response to the request, postprocessed
100: *
101: * @throws HttpException in case of a protocol or processing problem
102: * @throws IOException in case of an I/O problem
103: */
104: public HttpResponse execute(final HttpRequest request,
105: final HttpClientConnection conn, final HttpContext context)
106: throws IOException, HttpException {
107: if (request == null) {
108: throw new IllegalArgumentException(
109: "HTTP request may not be null");
110: }
111: if (conn == null) {
112: throw new IllegalArgumentException(
113: "Client connection may not be null");
114: }
115: if (context == null) {
116: throw new IllegalArgumentException(
117: "HTTP context may not be null");
118: }
119:
120: try {
121: HttpResponse response = doSendRequest(request, conn,
122: context);
123: if (response == null) {
124: response = doReceiveResponse(request, conn, context);
125: }
126: return response;
127: } catch (IOException ex) {
128: conn.close();
129: throw ex;
130: } catch (HttpException ex) {
131: conn.close();
132: throw ex;
133: } catch (RuntimeException ex) {
134: conn.close();
135: throw ex;
136: }
137: }
138:
139: /**
140: * Prepare a request for sending.
141: *
142: * @param request the request to prepare
143: * @param processor the processor to use
144: * @param context the context for sending the request
145: *
146: * @throws HttpException in case of a protocol or processing problem
147: * @throws IOException in case of an I/O problem
148: */
149: public void preProcess(final HttpRequest request,
150: final HttpProcessor processor, final HttpContext context)
151: throws HttpException, IOException {
152: if (request == null) {
153: throw new IllegalArgumentException(
154: "HTTP request may not be null");
155: }
156: if (processor == null) {
157: throw new IllegalArgumentException(
158: "HTTP processor may not be null");
159: }
160: if (context == null) {
161: throw new IllegalArgumentException(
162: "HTTP context may not be null");
163: }
164: processor.process(request, context);
165: }
166:
167: /**
168: * Send a request over a connection.
169: * This method also handles the expect-continue handshake if necessary.
170: * If it does not have to handle an expect-continue handshake, it will
171: * not use the connection for reading or anything else that depends on
172: * data coming in over the connection.
173: *
174: * @param request the request to send, already
175: * {@link #preProcess preprocessed}
176: * @param conn the connection over which to send the request,
177: * already established
178: * @param context the context for sending the request
179: *
180: * @return a terminal response received as part of an expect-continue
181: * handshake, or
182: * <code>null</code> if the expect-continue handshake is not used
183: *
184: * @throws HttpException in case of a protocol or processing problem
185: * @throws IOException in case of an I/O problem
186: */
187: protected HttpResponse doSendRequest(final HttpRequest request,
188: final HttpClientConnection conn, final HttpContext context)
189: throws IOException, HttpException {
190: if (request == null) {
191: throw new IllegalArgumentException(
192: "HTTP request may not be null");
193: }
194: if (conn == null) {
195: throw new IllegalArgumentException(
196: "HTTP connection may not be null");
197: }
198: if (context == null) {
199: throw new IllegalArgumentException(
200: "HTTP context may not be null");
201: }
202:
203: HttpResponse response = null;
204: context.setAttribute(ExecutionContext.HTTP_REQ_SENT,
205: Boolean.FALSE);
206:
207: conn.sendRequestHeader(request);
208: if (request instanceof HttpEntityEnclosingRequest) {
209: // Check for expect-continue handshake. We have to flush the
210: // headers and wait for an 100-continue response to handle it.
211: // If we get a different response, we must not send the entity.
212: boolean sendentity = true;
213: final ProtocolVersion ver = request.getRequestLine()
214: .getProtocolVersion();
215: if (((HttpEntityEnclosingRequest) request).expectContinue()
216: && !ver.lessEquals(HttpVersion.HTTP_1_0)) {
217:
218: conn.flush();
219: // As suggested by RFC 2616 section 8.2.3, we don't wait for a
220: // 100-continue response forever. On timeout, send the entity.
221: int tms = request.getParams().getIntParameter(
222: CoreProtocolPNames.WAIT_FOR_CONTINUE, 2000);
223:
224: if (conn.isResponseAvailable(tms)) {
225: response = conn.receiveResponseHeader();
226: if (canResponseHaveBody(request, response)) {
227: conn.receiveResponseEntity(response);
228: }
229: int status = response.getStatusLine()
230: .getStatusCode();
231: if (status < 200) {
232: if (status != HttpStatus.SC_CONTINUE) {
233: throw new ProtocolException(
234: "Unexpected response: "
235: + response.getStatusLine());
236: }
237: // discard 100-continue
238: response = null;
239: } else {
240: sendentity = false;
241: }
242: }
243: }
244: if (sendentity) {
245: conn
246: .sendRequestEntity((HttpEntityEnclosingRequest) request);
247: }
248: }
249: conn.flush();
250: context.setAttribute(ExecutionContext.HTTP_REQ_SENT,
251: Boolean.TRUE);
252: return response;
253: }
254:
255: /**
256: * Wait for and receive a response.
257: * This method will automatically ignore intermediate responses
258: * with status code 1xx.
259: *
260: * @param request the request for which to obtain the response
261: * @param conn the connection over which the request was sent
262: * @param context the context for receiving the response
263: *
264: * @return the final response, not yet post-processed
265: *
266: * @throws HttpException in case of a protocol or processing problem
267: * @throws IOException in case of an I/O problem
268: */
269: protected HttpResponse doReceiveResponse(final HttpRequest request,
270: final HttpClientConnection conn, final HttpContext context)
271: throws HttpException, IOException {
272: if (request == null) {
273: throw new IllegalArgumentException(
274: "HTTP request may not be null");
275: }
276: if (conn == null) {
277: throw new IllegalArgumentException(
278: "HTTP connection may not be null");
279: }
280: if (context == null) {
281: throw new IllegalArgumentException(
282: "HTTP context may not be null");
283: }
284:
285: HttpResponse response = null;
286: int statuscode = 0;
287:
288: while (response == null || statuscode < HttpStatus.SC_OK) {
289:
290: response = conn.receiveResponseHeader();
291: if (canResponseHaveBody(request, response)) {
292: conn.receiveResponseEntity(response);
293: }
294: statuscode = response.getStatusLine().getStatusCode();
295:
296: } // while intermediate response
297:
298: return response;
299:
300: }
301:
302: /**
303: * Finish a response.
304: * This includes post-processing of the response object.
305: * It does <i>not</i> read the response entity, if any.
306: * It does <i>not</i> allow for immediate re-use of the
307: * connection over which the response is coming in.
308: *
309: * @param response the response object to finish
310: * @param processor the processor to use
311: * @param context the context for post-processing the response
312: *
313: * @throws HttpException in case of a protocol or processing problem
314: * @throws IOException in case of an I/O problem
315: */
316: public void postProcess(final HttpResponse response,
317: final HttpProcessor processor, final HttpContext context)
318: throws HttpException, IOException {
319: if (response == null) {
320: throw new IllegalArgumentException(
321: "HTTP response may not be null");
322: }
323: if (processor == null) {
324: throw new IllegalArgumentException(
325: "HTTP processor may not be null");
326: }
327: if (context == null) {
328: throw new IllegalArgumentException(
329: "HTTP context may not be null");
330: }
331: processor.process(response, context);
332: }
333:
334: } // class HttpRequestExecutor
|