Index: trunk/http-core/src/java/org/apache/http/protocol/HttpRequestExecutor.java =================================================================== --- trunk/http-core/src/java/org/apache/http/protocol/HttpRequestExecutor.java (revision 377189) +++ trunk/http-core/src/java/org/apache/http/protocol/HttpRequestExecutor.java (working copy) @@ -35,6 +35,7 @@ import org.apache.http.HttpClientConnection; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpException; +import org.apache.http.HttpHost; import org.apache.http.HttpMutableRequest; import org.apache.http.HttpMutableResponse; import org.apache.http.HttpRequest; @@ -57,31 +58,131 @@ */ public class HttpRequestExecutor extends AbstractHttpProcessor { - private static final int WAIT_FOR_CONTINUE_MS = 10000; + protected static final int WAIT_FOR_CONTINUE_MS = 10000; + + /** The context holding the default context information. */ + protected final HttpContext defaultContext; + + /** The operations to use for executing requests. */ + protected final Operations operations; + - private final HttpContext context; - private HttpParams params = null; private HttpRequestRetryHandler retryhandler = null; - + + + /** + * Create a new request executor with default context information. + * The attributes in the argument context will be made available + * in the context used for executing a request. + * + * @param parentContext the default context information, + * or null + */ public HttpRequestExecutor(final HttpContext parentContext) { super(); - this.context = new HttpExecutionContext(parentContext); + this.defaultContext = new HttpExecutionContext(parentContext); + this.operations = createOperations(); } - + + /** + * Create a new request executor. + */ public HttpRequestExecutor() { this(null); } - - public HttpParams getParams() { + + + /** + * Create an instance of the {@link Operations operations}. + * This method is called once only, when the executor is created. + * Derived classes can override it to return an instance of a + * class derived from {@link Operations Operations}. + * + * @return the operations to use when executing requests + */ + protected Operations createOperations() { + return new Operations(); + } + + /** + * Not designed for use by application developers. + * Obtain the operations used by this executor. + * The operations are used internally by the request executor. + * This method makes them available to asynchronous dispatchers. + * Application developers should not bother with these operations at all. + * + * @return the operations + */ + public final Operations getOperations() { + return this.operations; + } + + + /** + * Obtain the default context information. + * This is not necessarily the same object passed to the constructor, + * but the default context information will be available here. + * + * @return the context holding the default context information + */ + public final HttpContext getContext() { + return this.defaultContext; + } + + /** + * Obtain the parameters for executing requests. + * + * @return the currently installed parameters + */ + public final HttpParams getParams() { return this.params; } - public void setParams(final HttpParams params) { + /** + * Set new parameters for executing requests. + * + * @param params the new parameters to use from now on + */ + public final void setParams(final HttpParams params) { this.params = params; } - - private boolean canResponseHaveBody(final HttpRequest request, final HttpResponse response) { + + /** + * Obtain the retry handler. + * + * @return the handler deciding whether a request should be retried + */ + public final HttpRequestRetryHandler getRetryHandler() { + return this.retryhandler; + } + + /** + * Set the retry handler. + * + * @param retryhandler the handler to decide whether a request + * should be retried + */ + public final void setRetryHandler(final HttpRequestRetryHandler retryhandler) { + this.retryhandler = retryhandler; + } + + + /** + * Decide whether a response comes with an entity. + * The implementation in this class is based on RFC 2616. + * Unknown methods and response codes are supposed to + * indicate responses with an entity. + *
+ * Derived executors can override this method to handle + * methods and response codes not specified in RFC 2616. + * + * @param request the request, to obtain the executed method + * @param response the response, to obtain the status code + */ + protected boolean canResponseHaveBody(final HttpRequest request, + final HttpResponse response) { + if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) { return false; } @@ -92,57 +193,50 @@ && status != HttpStatus.SC_RESET_CONTENT; } - private HttpMutableResponse doExecute( + + /** + * Send a request and obtain the response, without pre- and postprocessing. + * This method uses the default {@link #getContext context} + * and handles expect-continue handshake if necessary. + * + * @param request the request to send, already + * {@link Operations#prepareRequest prepared} + * @param conn the connection over which to send the request, already + * {@link Operations#prepareConnection prepared} + * + * @throws HttpException in case of a protocol problem + * @throws IOException in case of an IO problem + */ + protected HttpMutableResponse doExecute( final HttpRequest request, final HttpClientConnection conn) throws IOException, HttpException { - HttpMutableResponse response = null; - this.context.setAttribute(HttpExecutionContext.HTTP_REQ_SENT, - new Boolean(false)); - // Send request header - conn.sendRequestHeader(request); - if (request instanceof HttpEntityEnclosingRequest) { - HttpVersion ver = request.getRequestLine().getHttpVersion(); - if (ver.greaterEquals(HttpVersion.HTTP_1_1) - && ((HttpEntityEnclosingRequest)request).expectContinue()) { - // Flush headers - conn.flush(); - if (conn.isResponseAvailable(WAIT_FOR_CONTINUE_MS)) { - response = conn.receiveResponseHeader(this.params); - if (canResponseHaveBody(request, response)) { - conn.receiveResponseEntity(response); - } - int status = response.getStatusLine().getStatusCode(); - if (status < 200) { - if (status != HttpStatus.SC_CONTINUE) { - throw new ProtocolException("Unexpected response: " + - response.getStatusLine()); - } - } else { - return response; - } - } - } - conn.sendRequestEntity((HttpEntityEnclosingRequest) request); - } - conn.flush(); - - this.context.setAttribute(HttpExecutionContext.HTTP_REQ_SENT, - new Boolean(true)); - for (;;) { - // Loop until non 1xx resposne is received - response = conn.receiveResponseHeader(this.params); - if (canResponseHaveBody(request, response)) { - conn.receiveResponseEntity(response); - } - int statuscode = response.getStatusLine().getStatusCode(); - if (statuscode >= HttpStatus.SC_OK) { - break; - } - } + + HttpMutableResponse response = + operations.transmitRequest(request, this.defaultContext, conn, + true); // handle expect-continue + + if (response == null) + response = operations.obtainResponse(request, conn, true); + return response; } - - public HttpResponse execute(final HttpRequest request, final HttpClientConnection conn) + + + /** + * Synchronously send a request and obtain the response. + * + * @param request the request to send. It will be preprocessed. + * @param conn the connection over which to send. + * The {@link HttpClientConnection#setTargetHost target} + * host has to be set before calling this method. + * + * @return the response to the request, postprocessed + * + * @throws HttpException in case of a protocol or processing problem + * @throws IOException in case of an IO problem + */ + public HttpResponse execute(final HttpRequest request, + final HttpClientConnection conn) throws IOException, HttpException { if (request == null) { @@ -151,32 +245,16 @@ if (conn == null) { throw new IllegalArgumentException("Client connection may not be null"); } - this.context.setAttribute(HttpExecutionContext.HTTP_REQUEST, request); - this.context.setAttribute(HttpExecutionContext.HTTP_CONNECTION, conn); - this.context.setAttribute(HttpExecutionContext.HTTP_TARGET_HOST, - conn.getTargetHost()); - // Link own parameters as defaults - request.getParams().setDefaults(this.params); - - if (request instanceof HttpMutableRequest) { - preprocessRequest((HttpMutableRequest)request, this.context); - } - + operations.prepareRequest(request, conn.getTargetHost(), + null, conn); + HttpMutableResponse response = null; // loop until the method is successfully processed, the retryHandler // returns false or a non-recoverable exception is thrown for (int execCount = 0; ; execCount++) { try { - if (HttpConnectionParams.isStaleCheckingEnabled(this.params)) { - if (conn.isOpen() && conn.isStale()) { - conn.close(); - } - } - if (!conn.isOpen()) { - conn.open(this.params); - // TODO: Implement secure tunnelling - } + operations.prepareConnection(conn, null, null); response = doExecute(request, conn); // exit retry loop break; @@ -196,16 +274,315 @@ throw ex; } } - postprocessResponse(response, this.context); + + operations.finishResponse(response, null); + return response; - } - public HttpRequestRetryHandler getRetryHandler() { - return this.retryhandler; - } + } // execute - public void setRetryHandler(final HttpRequestRetryHandler retryhandler) { - this.retryhandler = retryhandler; - } - -} + + + // ======================================================================= + + /** + * Not designed for use by application developers. + * Provides fine-grained operations for request execution. + * Application developers should not bother to use these. + * + * @author Oleg Kalnichevski + * @author Roland Weber + * + * @version $Revision$ + * + * @since 4.0 + */ + public class Operations { + + /** + * Restricted default constructor. + */ + protected Operations() { + // no body + } + + /** + * Prepare a request for sending. + * + * @param request the request to prepare + * @param target the target host for the request + * @param context the context for sending the request, + * or null to use the default + * {@link #getContext context} + * @param conn the connection for sending the request, + * or null if not yet known + * + * @throws HttpException if the request can not be prepared + * @throws IOException in case of an IO problem + */ + public void prepareRequest(HttpRequest request, + HttpHost target, + HttpContext context, + HttpClientConnection conn) + throws HttpException, IOException { + + if (request == null) + throw new IllegalArgumentException + ("request must not be null"); + if (request == null) + throw new IllegalArgumentException + ("target host must not be null"); + + if (context == null) + context = HttpRequestExecutor.this.defaultContext; + + context.setAttribute(HttpExecutionContext.HTTP_REQUEST, + request); + //@@@ behavior if proxying - set real target or proxy, or both? + context.setAttribute(HttpExecutionContext.HTTP_TARGET_HOST, + target); + if (conn != null) + context.setAttribute(HttpExecutionContext.HTTP_CONNECTION, + conn); + else + context.removeAttribute(HttpExecutionContext.HTTP_CONNECTION); + + // link default parameters + request.getParams().setDefaults(HttpRequestExecutor.this.params); + + if (request instanceof HttpMutableRequest) + preprocessRequest((HttpMutableRequest) request, context); + + } // prepareRequest + + + /** + * Prepare a connection before sending a request. + * + * @param conn the connection to prepare + * @param target the target host for the request, or + * null to send to the host already + * set as the connection target + * @param params the parameters for preparing the connection, or + * null to use the default + * {@link #getParams parameters} + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + public void prepareConnection(HttpClientConnection conn, + HttpHost target, + HttpParams params) + throws HttpException, IOException { + + if (conn == null) + throw new IllegalArgumentException + ("connection must not be null"); + + if (params == null) + params = HttpRequestExecutor.this.params; + + // make sure the connection is open and points to the target host + if ((target == null) || + target.equals(conn.getTargetHost())) { + + // host+port ok, check whether connection needs to be opened + if (HttpConnectionParams.isStaleCheckingEnabled(params)) { + if (conn.isOpen() && conn.isStale()) { + conn.close(); + } + } + if (!conn.isOpen()) { + conn.open(params); + //TODO: Implement secure tunnelling @@@ + } + + } else { + + // wrong target, point connection to target + if (conn.isOpen()) + conn.close(); + conn.setTargetHost(target); + conn.open(params); + + } // if connection points to target else + + } // prepareConnection + + + /** + * Send a request over a connection. + * This method also handles the expect-continue handshake, + * if requested. + * + * @param request the request to send, already + * {@link #prepareRequest prepared} + * @param context the context for sending the request, + * or null to use the default + * {@link #getContext context} + * @param conn the connection over which to send the request, + * already {@link #prepareConnection prepared} + * @param exconhs true if an expect-continue handshake + * should be handled, or + * false to disable it. + * If false is passed, this method will + * exclusively send over the connection, otherwise it + * may also have to receive responses. + * + * @return a terminal response received as part of an expect-continue + * handshake. Always null if the + * exconhs argument is false. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + public HttpMutableResponse transmitRequest( + final HttpRequest request, + HttpContext context, + final HttpClientConnection conn, + final boolean exconhs) + + throws IOException, HttpException { + + if (request == null) + throw new IllegalArgumentException + ("request must not be null"); + if (conn == null) + throw new IllegalArgumentException + ("connection must not be null"); + + if (context == null) + context = HttpRequestExecutor.this.defaultContext; + + HttpMutableResponse response = null; + context.setAttribute(HttpExecutionContext.HTTP_REQ_SENT, + Boolean.FALSE); + + conn.sendRequestHeader(request); + if (request instanceof HttpEntityEnclosingRequest) { + final HttpEntityEnclosingRequest heer = + (HttpEntityEnclosingRequest) request; + + // Check for expect-continue handshake. We have to flush the + // headers and wait for an 100-continue response to handle it. + // If we get a different response, we must not send the entity. + boolean sendentity = true; + final HttpVersion ver = + request.getRequestLine().getHttpVersion(); + if (exconhs && heer.expectContinue() && + ver.greaterEquals(HttpVersion.HTTP_1_1)) { + + conn.flush(); + // As suggested by RFC 2616 section 8.2.3, we don't wait + // for a 100-continue response forever. On timeout, assume + // a non-compliant server and send the entity anyway. + if (conn.isResponseAvailable(WAIT_FOR_CONTINUE_MS)) { + + response = obtainResponse(request, conn, false); + int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + //@@@ TODO: is this in line with RFC 2616, 10.1? + if (status != HttpStatus.SC_CONTINUE) { + throw new ProtocolException + ("Unexpected response: " + + response.getStatusLine()); + } + } else { + sendentity = false; + } + } + } + if (sendentity) + conn.sendRequestEntity(heer); + } + conn.flush(); + + context.setAttribute(HttpExecutionContext.HTTP_REQ_SENT, + Boolean.TRUE); + + return response; + + } // transmitRequest + + + /** + * Wait for and receive a response. + * + * @param request the request for which to obtain the response + * @param conn the connection over which the request was sent + * @param terminal true to wait for a terminal response, + * or false if informational responses + * with status code 1xx should be returned + * + * @return the response, not yet post-processed + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + public HttpMutableResponse obtainResponse( + final HttpRequest request, + final HttpClientConnection conn, + final boolean terminal) + throws HttpException, IOException { + + if (request == null) + throw new IllegalArgumentException + ("request must not be null"); + if (conn == null) + throw new IllegalArgumentException + ("connection must not be null"); + + // see HttpRequestExecutor.doExecute, final part + HttpMutableResponse response = null; + int statuscode = 0; + + while ((response == null) || + (terminal && (statuscode < HttpStatus.SC_OK))) { + + response = conn.receiveResponseHeader(request.getParams()); + if (canResponseHaveBody(request, response)) { + conn.receiveResponseEntity(response); + } + statuscode = response.getStatusLine().getStatusCode(); + + } // while intermediate response + + return response; + + } // obtainResponse + + + /** + * Finish a response. + * This includes post-processing of the response object. + * It does not read the response entity, if any. + * It does not allow for immediate re-use of the + * connection over which the response is coming in. + * + * @param response the response object to finish + * @param context the context for post-processing the response, or + * null to use the default + * {@link #getContext context} + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + public void finishResponse(HttpMutableResponse response, + HttpContext context) + throws HttpException, IOException { + + if (response == null) + throw new IllegalArgumentException + ("response must not be null"); + + if (context == null) + context = HttpRequestExecutor.this.defaultContext; + + postprocessResponse(response, context); + + } // finishResponse + + } // class Operations + + +} // class HttpRequestExecutor Index: trunk/http-core/src/examples/org/apache/http/examples/ElementalHttpServer.java =================================================================== --- trunk/http-core/src/examples/org/apache/http/examples/ElementalHttpServer.java (revision 377149) +++ trunk/http-core/src/examples/org/apache/http/examples/ElementalHttpServer.java (working copy) @@ -94,7 +94,7 @@ StringEntity body = new StringEntity("File not found", "UTF-8"); response.setEntity(body); System.out.println("File " + file.getPath() + " not found"); - } else if (!file.canRead()) { + } else if (!file.canRead() || file.isDirectory()) { response.setStatusCode(HttpStatus.SC_FORBIDDEN); StringEntity body = new StringEntity("Access Denied", "UTF-8"); response.setEntity(body); Index: trunk/http-async/src/java/org/apache/http/async/impl/SimpleHttpDispatcher.java =================================================================== --- trunk/http-async/src/java/org/apache/http/async/impl/SimpleHttpDispatcher.java (revision 377149) +++ trunk/http-async/src/java/org/apache/http/async/impl/SimpleHttpDispatcher.java (working copy) @@ -45,10 +45,10 @@ import org.apache.http.ConnectionReuseStrategy; import org.apache.http.HttpException; import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestExecutor; import org.apache.http.params.HttpParams; import org.apache.http.async.HttpHandle; import org.apache.http.async.HttpDispatcher; -import org.apache.http.async.AsyncHttpProcessor; import org.apache.http.async.AbstractHttpHandle; import org.apache.http.async.AbstractHttpDispatcher; @@ -78,22 +78,20 @@ public class SimpleHttpDispatcher extends AbstractHttpDispatcher implements HttpDispatcher { // @@@ move some of this stuff to an abstract base class? + // @@@ the HttpRequestExecutor and getDefaultContext method, for example /** The one and only connection. */ private final HttpClientConnection client_connection; /** - * The HTTP processor. + * The request executor. * For pre- and postprocessing of requests and responses, respectively. */ - protected final AsyncHttpProcessor async_processor; + protected final HttpRequestExecutor request_executor; /** The connection re-use strategy. */ private ConnectionReuseStrategy reuse_strategy; - /** The parameters to use. */ - private HttpParams http_params; - /** The background thread. */ private SimpleHttpDispatcherThread background_thread; @@ -127,29 +125,26 @@ * Create a new simple HTTP dispatcher. * * @param conn the connection to use - * @param proc the HTTP processor to use, - * including the default context + * @param reqexec the request processor to use, + * including the default context and parameters * @param reuse the strategy for re-using connections, or * null to disable connection re-use - * @param params the parameters to use */ public SimpleHttpDispatcher(HttpClientConnection conn, - AsyncHttpProcessor proc, - ConnectionReuseStrategy reuse, - HttpParams params) { + HttpRequestExecutor reqexec, + ConnectionReuseStrategy reuse) { super(null); if (conn == null) - throw new IllegalArgumentException("connection must not be null"); - if (proc == null) - throw new IllegalArgumentException("processor must not be null"); - if (params == null) - throw new IllegalArgumentException("parameters must not be null"); + throw new IllegalArgumentException + ("connection must not be null"); + if (reqexec == null) + throw new IllegalArgumentException + ("request executor must not be null"); client_connection = conn; - async_processor = proc; + request_executor = reqexec; reuse_strategy = reuse; - http_params = params; request_handle_queue = new LinkedList(); response_handles = new LinkedList(); @@ -184,7 +179,7 @@ //@@@ - for now: no proxy (how to indicate proxy in the first place?) HttpContext ctxt = prepareRequest - (async_processor, request, target, http_params, context); + (request_executor, request, target, context); SimpleHttpHandle hdl = new SimpleHttpHandle(this, request, ctxt); // linked handles need to be tracked for abortAll() @@ -218,7 +213,7 @@ throw new IllegalArgumentException("response must not be null"); handle.checkDispatcher(this); - finishResponse(async_processor, response, handle.getContext()); + finishResponse(request_executor, response, handle.getContext()); } // postprocessResponse @@ -227,7 +222,7 @@ // non-javadoc, see interface HttpDispatcher public final HttpContext getDefaultContext() { - return async_processor.getDefaultContext(); + return request_executor.getContext(); } @@ -392,14 +387,14 @@ } //@@@ TODO: retry handling! - transmitRequest(async_processor, + transmitRequest(request_executor, handle.getRequest(), handle.getContext(), client_connection); //@@@ TODO: timeout handling! HttpMutableResponse response = - obtainResponse(async_processor, + obtainResponse(request_executor, handle.getRequest(), client_connection); handle.provideResponse(response); Index: trunk/http-async/src/java/org/apache/http/async/AbstractHttpDispatcher.java =================================================================== --- trunk/http-async/src/java/org/apache/http/async/AbstractHttpDispatcher.java (revision 377149) +++ trunk/http-async/src/java/org/apache/http/async/AbstractHttpDispatcher.java (working copy) @@ -41,12 +41,12 @@ import org.apache.http.HttpRequest; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpExecutionContext; +import org.apache.http.protocol.HttpRequestExecutor; /** * Abstract base for implementations of {@link HttpDispatcher HttpDispatcher}. - * Provides access to protected methods in - * {@link AsyncHttpProcessor AsyncHttpProcessor}. * * @version $Revision$ $Date$ * @@ -121,12 +121,13 @@ /** * Prepare a request for sending. - * Maps to {@link AsyncHttpProcessor#prepareRequest proc.prepareRequest}. + * Maps to {@link + * org.apache.http.protocol.HttpRequestExecutor.Operations#prepareRequest + * reqexec.getOperations().prepareRequest}. * - * @param proc the processor to use for preparing + * @param reqexec the request executor to use * @param request the request to prepare * @param target the target host for the request - * @param params the default parameters * @param context the parent context for sending the request, * or null to use the default context * @@ -135,50 +136,78 @@ * @throws HttpException if the request can not be prepared * @throws IOException in case of an IO problem */ - protected static HttpContext prepareRequest(AsyncHttpProcessor proc, - HttpRequest request, - HttpHost target, - HttpParams params, - HttpContext context) + protected static HttpContext prepareRequest(HttpRequestExecutor reqexec, + HttpRequest request, + HttpHost target, + HttpContext context) throws HttpException, IOException { - return proc.prepareRequest(request, target, params, context); + HttpExecutionContext hxc = new HttpExecutionContext(context); + // argument checking is done here... + reqexec.getOperations().prepareRequest(request, target, hxc, null); + + return hxc; + } // prepareRequest /** * Send a request. - * Maps to {@link AsyncHttpProcessor#transmitRequest proc.transmitRequest}. + * Calls {@link + * org.apache.http.protocol.HttpRequestExecutor.Operations#prepareConnection + * reqexec.getOperations().prepareConnection} + * and {@link + * org.apache.http.protocol.HttpRequestExecutor.Operations#transmitRequest + * reqexec.getOperations().transmitRequest}. * - * @param proc the processor to use for transmission - * @param request the request to send, already prepared - * @param context the request specific context returned by - * {@link #prepareRequest prepareRequest} - * when the request was prepared - * @param connection the connection over which to send the request + * @param reqexec the request executor to use + * @param request the request to send, already prepared + * @param context the request specific context returned by + * {@link #prepareRequest prepareRequest} + * when the request was prepared + * @param conn the connection over which to send the request * * @throws HttpException in case of a problem * @throws IOException in case of an IO problem */ - protected static void transmitRequest(AsyncHttpProcessor proc, + protected static void transmitRequest(HttpRequestExecutor reqexec, HttpRequest request, HttpContext context, - HttpClientConnection connection) + HttpClientConnection conn) throws HttpException, IOException { - proc.transmitRequest(request, context, connection); + if (request == null) + throw new IllegalArgumentException("request must not be null"); + if (context == null) + throw new IllegalArgumentException("context must not be null"); + if (conn == null) + throw new IllegalArgumentException("connection must not be null"); + HttpHost target = (HttpHost) + context.getAttribute(HttpExecutionContext.HTTP_TARGET_HOST); + if (target == null) + throw new IllegalStateException + ("target host missing in request context"); + + reqexec.getOperations(). + prepareConnection(conn, target, request.getParams()); + reqexec.getOperations(). + transmitRequest(request, context, conn, + false); // no expect-continue, returns null + } // transmitRequest /** * Wait for and receive a response. - * Maps to {@link AsyncHttpProcessor#obtainResponse proc.obtainResponse}. + * Maps to {@link + * org.apache.http.protocol.HttpRequestExecutor.Operations#obtainResponse + * reqexec.getOperations().obtainResponse}. * - * @param proc the processor to use for transmission - * @param request the request for which to obtain the response - * @param connection the connection over which the request was sent + * @param reqexec the request executor to use + * @param request the request for which to obtain the response + * @param conn the connection over which the request was sent * * @return the response, not yet post-processed * @@ -186,34 +215,37 @@ * @throws IOException in case of an IO problem */ protected static - HttpMutableResponse obtainResponse(AsyncHttpProcessor proc, + HttpMutableResponse obtainResponse(HttpRequestExecutor reqexec, HttpRequest request, - HttpClientConnection connection) + HttpClientConnection conn) throws HttpException, IOException { - return proc.obtainResponse(request, connection); + return reqexec.getOperations().obtainResponse(request, conn, true); } // obtainResponse /** * Finish a response. - * Maps to {@link AsyncHttpProcessor#finishResponse proc.finishResponse}. + * Maps to {@link + * org.apache.http.protocol.HttpRequestExecutor.Operations#finishResponse + * reqexec.getOperations().finishResponse}. * - * @param response the response object to finish - * @param context the context obtained from - * {@link #prepareRequest prepareRequest} - * for the matching request + * @param reqexec the request executor to use + * @param response the response object to finish + * @param context the context obtained from + * {@link #prepareRequest prepareRequest} + * for the matching request * * @throws HttpException in case of a problem * @throws IOException in case of an IO problem */ - protected static void finishResponse(AsyncHttpProcessor proc, + protected static void finishResponse(HttpRequestExecutor reqexec, HttpMutableResponse response, HttpContext context) throws HttpException, IOException { - proc.finishResponse(response, context); + reqexec.getOperations().finishResponse(response, context); } // finishResponse Index: trunk/http-async/src/java/org/apache/http/async/AsyncHttpProcessor.java =================================================================== --- trunk/http-async/src/java/org/apache/http/async/AsyncHttpProcessor.java (revision 377149) +++ trunk/http-async/src/java/org/apache/http/async/AsyncHttpProcessor.java (working copy) @@ -1,322 +0,0 @@ -/* - * $HeadURL$ - * $Revision$ - * $Date$ - * - * ==================================================================== - * - * Copyright 2006 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.http.async; - - -import java.io.IOException; - -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpMutableRequest; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpResponse; -import org.apache.http.HttpMutableResponse; -import org.apache.http.HttpClientConnection; -import org.apache.http.HttpStatus; -import org.apache.http.HttpException; -import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.HttpExecutionContext; -import org.apache.http.protocol.AbstractHttpProcessor; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpConnectionParams; - - - -/** - * HTTP processor for asynchronously dispatched requests. - * This is the asynchronous equivalent to - * {@link org.apache.http.protocol.HttpRequestExecutor HttpRequestExecutor}. - * - * - * @version $Revision$ $Date$ - * - * @since 4.0 - */ -public class AsyncHttpProcessor extends AbstractHttpProcessor { - - /** The default (parent) context. */ - private HttpContext default_context; - - - /** - * Create a new HTTP processor with the given default context. - * - * @param context the default context - */ - public AsyncHttpProcessor(HttpContext context) { - - if (context == null) - throw new IllegalArgumentException - ("default context must not be null"); - - default_context = context; - } - - - /** - * Create a new HTTP processor with empty default context. - */ - public AsyncHttpProcessor() { - this(new HttpExecutionContext(null)); - } - - /** - * Obtain the default context. - * - * @return the default context, or null if there is none - */ - public final HttpContext getDefaultContext() { - - return default_context; - } - - - /** - * Prepare a request for sending. - * - * @param request the request to prepare - * @param target the target host for the request - * @param params the default parameters - * @param context the parent context for sending the request, - * or null to use the default context - * - * @return a new context specific to the request - * - * @throws HttpException if the request can not be prepared - * @throws IOException in case of an IO problem - */ - protected HttpContext prepareRequest(HttpRequest request, - HttpHost target, - HttpParams params, - HttpContext context) - throws HttpException, IOException { - - if (request == null) - throw new IllegalArgumentException("request must not be null"); - if (context == null) - context = default_context; - - // see also HttpRequestExecutor.execute(), initial part - - HttpExecutionContext hxc = new HttpExecutionContext(context); - hxc.setAttribute(HttpExecutionContext.HTTP_REQUEST, request); - //@@@ behavior if proxying - set real target or proxy, or both? - hxc.setAttribute(HttpExecutionContext.HTTP_TARGET_HOST, target); - - //@@@ Can't set connection in the context, it's not available yet. - //@@@ Is it required for one of the interceptors? - - // link default parameters - request.getParams().setDefaults(params); - - if (request instanceof HttpMutableRequest) - preprocessRequest((HttpMutableRequest) request, hxc); - - return hxc; - - } // prepareRequest - - - /** - * Send a request. - * - * @param request the request to send, already prepared - * @param context the request specific context returned by - * {@link #prepareRequest prepareRequest} - * when the request was prepared - * @param connection the connection over which to send the request - * - * @throws HttpException in case of a problem - * @throws IOException in case of an IO problem - */ - protected void transmitRequest(HttpRequest request, - HttpContext context, - HttpClientConnection connection) - throws HttpException, IOException { - - if (request == null) - throw new IllegalArgumentException("request must not be null"); - if (context == null) - throw new IllegalArgumentException("context must not be null"); - if (connection == null) - throw new IllegalArgumentException("connection must not be null"); - - HttpHost target = (HttpHost) - context.getAttribute(HttpExecutionContext.HTTP_TARGET_HOST); - if (target == null) - throw new IllegalStateException - ("target host missing in request context"); - - // see also HttpRequestExecutor.execute() and .doExecute() - // Retry handling should be implemented by caller, since the retry - // may be triggered by receiving the response, too. - - // make sure the connection is open and points to the target host - HttpParams params = request.getParams(); - if (target.equals(connection.getTargetHost())) { - - // host and port ok, check whether connection needs to be opened - if (HttpConnectionParams.isStaleCheckingEnabled(params)) { - if (connection.isOpen() && connection.isStale()) { - connection.close(); - } - } - if (!connection.isOpen()) { - connection.open(params); - //TODO: Implement secure tunnelling (@@@ HttpRequestExecutor) - } - - } else { - - // wrong target, point connection to target - if (connection.isOpen()) - connection.close(); - connection.setTargetHost(target); - connection.open(params); - - } // if connection points to target else - - - // this is the initial part of HttpRequestExecutor.doExecute, - // minus the handling of expect/continue handshakes - - context.setAttribute(HttpExecutionContext.HTTP_REQ_SENT, - Boolean.FALSE); - - connection.sendRequestHeader(request); - if (request instanceof HttpEntityEnclosingRequest) { - connection.sendRequestEntity((HttpEntityEnclosingRequest) request); - } - connection.flush(); - - context.setAttribute(HttpExecutionContext.HTTP_REQ_SENT, - Boolean.TRUE); - - } // transmitRequest - - - /** - * Wait for and receive a response. - * clarify sematics - will the response body be received? - * - * @param request the request for which to obtain the response - * @param connection the connection over which the request was sent - * - * @return the response, not yet post-processed - * - * @throws HttpException in case of a problem - * @throws IOException in case of an IO problem - */ - protected - HttpMutableResponse obtainResponse(HttpRequest request, - HttpClientConnection connection) - throws HttpException, IOException { - - if (request == null) - throw new IllegalArgumentException("request must not be null"); - if (connection == null) - throw new IllegalArgumentException("connection must not be null"); - - // see HttpRequestExecutor.doExecute, final part - HttpMutableResponse response = null; - int statuscode = 0; - - // skip 1xx responses - while (statuscode < HttpStatus.SC_OK) { - response = connection.receiveResponseHeader(request.getParams()); - - //@@@ does this actually receive, or just wrap the stream? - //@@@ how to make sure we get only a wrapped stream here? - //@@@ don't call receiveResponseEntity here at all? - //@@@ could there be 1xx responses with an entity? check RFC 2616 - if (canResponseHaveBody(request, response)) { - connection.receiveResponseEntity(response); - } - statuscode = response.getStatusLine().getStatusCode(); - - } // while intermediate response - - return response; - - } // obtainResponse - - - /** - * Decide whether a response comes with an entity. - * - * @param request the request responded to - * @param response the response, initialized from headers only - * - * @return true if the response should have an entity, or - * false if it doesn't - */ - //@@@ duplicated from HttpRequestExecutor.canResponseHaveBody - private boolean canResponseHaveBody(final HttpRequest request, - final HttpResponse response) { - if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) { - return false; - } - int status = response.getStatusLine().getStatusCode(); - return status >= HttpStatus.SC_OK - && status != HttpStatus.SC_NO_CONTENT - && status != HttpStatus.SC_NOT_MODIFIED - && status != HttpStatus.SC_RESET_CONTENT; - } - - - /** - * Finish a response. - * This includes post-processing of the response object. - * It does not read the response entity (if any), nor allows - * re-use of the connection over which the response is coming in. - * - * @param response the response object to finish - * @param context the context obtained from - * {@link #prepareRequest prepareRequest} - * for the matching request - * - * @throws HttpException in case of a problem - * @throws IOException in case of an IO problem - */ - protected void finishResponse(HttpMutableResponse response, - HttpContext context) - throws HttpException, IOException { - - if (response == null) - throw new IllegalArgumentException("response must not be null"); - if (context == null) - throw new IllegalArgumentException("context must not be null"); - - postprocessResponse(response, context); - - } // finishResponse - - -} // class AsyncHttpProcessor Index: trunk/http-async/src/examples/org/apache/http/examples/ElementalAsyncGet.java =================================================================== --- trunk/http-async/src/examples/org/apache/http/examples/ElementalAsyncGet.java (revision 377149) +++ trunk/http-async/src/examples/org/apache/http/examples/ElementalAsyncGet.java (working copy) @@ -46,8 +46,8 @@ import org.apache.http.params.HttpProtocolParams; import org.apache.http.async.HttpDispatcher; import org.apache.http.async.HttpHandle; -import org.apache.http.async.AsyncHttpProcessor; import org.apache.http.async.impl.SimpleHttpDispatcher; +import org.apache.http.protocol.HttpRequestExecutor; import org.apache.http.protocol.RequestConnControl; import org.apache.http.protocol.RequestContent; import org.apache.http.protocol.RequestTargetHost; @@ -80,12 +80,12 @@ if ((targets == null) || (targets.length < 1)) { targets = new String[] { "/", - "/manual/", + "/servlets-examples/servlet/RequestInfoExample", "/somewhere%20in%20pampa" }; } - HttpHost host = new HttpHost("localhost", 80); + HttpHost host = new HttpHost("localhost", 8080); HttpHandle[] handles = new HttpHandle[targets.length]; for (int i = 0; i < targets.length; i++) { @@ -156,20 +156,20 @@ HttpProtocolParams.setUserAgent(params, "Jakarta-HttpComponents/1.1"); HttpProtocolParams.setUseExpectContinue(params, false); - HttpClientConnection conn = new DefaultHttpClientConnection(); - - AsyncHttpProcessor proc = new AsyncHttpProcessor(); + HttpRequestExecutor reqexec = new HttpRequestExecutor(); + reqexec.setParams(params); // Required request interceptors - proc.addInterceptor(new RequestContent()); - proc.addInterceptor(new RequestTargetHost()); + reqexec.addInterceptor(new RequestContent()); + reqexec.addInterceptor(new RequestTargetHost()); // Recommended request interceptors - proc.addInterceptor(new RequestConnControl()); - proc.addInterceptor(new RequestUserAgent()); - // not supported: proc.addInterceptor(new RequestExpectContinue()); + reqexec.addInterceptor(new RequestConnControl()); + reqexec.addInterceptor(new RequestUserAgent()); + // not supported: reqexec.addInterceptor(new RequestExpectContinue()); + HttpClientConnection conn = new DefaultHttpClientConnection(); ConnectionReuseStrategy crs = new DefaultConnectionReuseStrategy(); - HttpDispatcher hdp = new SimpleHttpDispatcher(conn, proc, crs, params); + HttpDispatcher hdp = new SimpleHttpDispatcher(conn, reqexec, crs); return hdp;