/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
* <http://www.apache.org/>.
*
*/
package org.apache.http.protocol;
import java.io.IOException;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.ProtocolException;
import org.apache.http.ProtocolVersion;
import org.apache.http.annotation.Contract;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.util.Args;
HttpRequestExecutor
is a client side HTTP protocol handler based on the blocking (classic) I/O model. HttpRequestExecutor
relies on HttpProcessor
to generate mandatory protocol headers for all outgoing messages and apply common, cross-cutting message transformations to all incoming and outgoing messages. Application specific processing can be implemented outside HttpRequestExecutor
once the request has been executed and a response has been received.
Since: 4.0
/**
* {@code HttpRequestExecutor} is a client side HTTP protocol handler based
* on the blocking (classic) I/O model.
* <p>
* {@code HttpRequestExecutor} relies on {@link HttpProcessor} to generate
* mandatory protocol headers for all outgoing messages and apply common,
* cross-cutting message transformations to all incoming and outgoing messages.
* Application specific processing can be implemented outside
* {@code HttpRequestExecutor} once the request has been executed and
* a response has been received.
*
* @since 4.0
*/
@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class HttpRequestExecutor {
public static final int DEFAULT_WAIT_FOR_CONTINUE = 3000;
private final int waitForContinue;
Creates new instance of HttpRequestExecutor.
Since: 4.3
/**
* Creates new instance of HttpRequestExecutor.
*
* @since 4.3
*/
public HttpRequestExecutor(final int waitForContinue) {
super();
this.waitForContinue = Args.positive(waitForContinue, "Wait for continue time");
}
public HttpRequestExecutor() {
this(DEFAULT_WAIT_FOR_CONTINUE);
}
Decide whether a response comes with an entity.
The implementation in this class is based on RFC 2616.
Derived executors can override this method to handle
methods and response codes not specified in RFC 2616.
Params: - request – the request, to obtain the executed method
- response – the response, to obtain the status code
/**
* Decide whether a response comes with an entity.
* The implementation in this class is based on RFC 2616.
* <p>
* Derived executors can override this method to handle
* methods and response codes not specified in RFC 2616.
* </p>
*
* @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;
}
final 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;
}
Sends the request and obtain a response.
Params: - request – the request to execute.
- conn – the connection over which to execute the request.
Throws: - IOException – in case of an I/O error.
- HttpException – in case of HTTP protocol violation or a processing
problem.
Returns: the response to the request.
/**
* Sends the request and obtain a response.
*
* @param request the request to execute.
* @param conn the connection over which to execute the request.
*
* @return the response to the request.
*
* @throws IOException in case of an I/O error.
* @throws HttpException in case of HTTP protocol violation or a processing
* problem.
*/
public HttpResponse execute(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws IOException, HttpException {
Args.notNull(request, "HTTP request");
Args.notNull(conn, "Client connection");
Args.notNull(context, "HTTP context");
try {
HttpResponse response = doSendRequest(request, conn, context);
if (response == null) {
response = doReceiveResponse(request, conn, context);
}
return response;
} catch (final IOException ex) {
closeConnection(conn);
throw ex;
} catch (final HttpException ex) {
closeConnection(conn);
throw ex;
} catch (final RuntimeException ex) {
closeConnection(conn);
throw ex;
}
}
private static void closeConnection(final HttpClientConnection conn) {
try {
conn.close();
} catch (final IOException ignore) {
}
}
Pre-process the given request using the given protocol processor and
initiates the process of request execution.
Params: - request – the request to prepare
- processor – the processor to use
- context – the context for sending the request
Throws: - IOException – in case of an I/O error.
- HttpException – in case of HTTP protocol violation or a processing
problem.
/**
* Pre-process the given request using the given protocol processor and
* initiates the process of request execution.
*
* @param request the request to prepare
* @param processor the processor to use
* @param context the context for sending the request
*
* @throws IOException in case of an I/O error.
* @throws HttpException in case of HTTP protocol violation or a processing
* problem.
*/
public void preProcess(
final HttpRequest request,
final HttpProcessor processor,
final HttpContext context) throws HttpException, IOException {
Args.notNull(request, "HTTP request");
Args.notNull(processor, "HTTP processor");
Args.notNull(context, "HTTP context");
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
processor.process(request, context);
}
Send the given request over the given connection.
This method also handles the expect-continue handshake if necessary.
If it does not have to handle an expect-continue handshake, it will
not use the connection for reading or anything else that depends on
data coming in over the connection.
Params: - request – the request to send, already
preprocessed
- conn – the connection over which to send the request,
already established
- context – the context for sending the request
Throws: - IOException – in case of an I/O error.
- HttpException – in case of HTTP protocol violation or a processing
problem.
Returns: a terminal response received as part of an expect-continue handshake, or null
if the expect-continue handshake is not used
/**
* Send the given request over the given connection.
* <p>
* This method also handles the expect-continue handshake if necessary.
* If it does not have to handle an expect-continue handshake, it will
* not use the connection for reading or anything else that depends on
* data coming in over the connection.
*
* @param request the request to send, already
* {@link #preProcess preprocessed}
* @param conn the connection over which to send the request,
* already established
* @param context the context for sending the request
*
* @return a terminal response received as part of an expect-continue
* handshake, or
* {@code null} if the expect-continue handshake is not used
*
* @throws IOException in case of an I/O error.
* @throws HttpException in case of HTTP protocol violation or a processing
* problem.
*/
protected HttpResponse doSendRequest(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws IOException, HttpException {
Args.notNull(request, "HTTP request");
Args.notNull(conn, "Client connection");
Args.notNull(context, "HTTP context");
HttpResponse response = null;
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.FALSE);
conn.sendRequestHeader(request);
if (request instanceof HttpEntityEnclosingRequest) {
// 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 ProtocolVersion ver =
request.getRequestLine().getProtocolVersion();
if (((HttpEntityEnclosingRequest) request).expectContinue() &&
!ver.lessEquals(HttpVersion.HTTP_1_0)) {
conn.flush();
// As suggested by RFC 2616 section 8.2.3, we don't wait for a
// 100-continue response forever. On timeout, send the entity.
if (conn.isResponseAvailable(this.waitForContinue)) {
response = conn.receiveResponseHeader();
if (canResponseHaveBody(request, response)) {
conn.receiveResponseEntity(response);
}
final int status = response.getStatusLine().getStatusCode();
if (status < 200) {
if (status != HttpStatus.SC_CONTINUE) {
throw new ProtocolException(
"Unexpected response: " + response.getStatusLine());
}
// discard 100-continue
response = null;
} else {
sendentity = false;
}
}
}
if (sendentity) {
conn.sendRequestEntity((HttpEntityEnclosingRequest) request);
}
}
conn.flush();
context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE);
return response;
}
Waits for and receives a response.
This method will automatically ignore intermediate responses
with status code 1xx.
Params: - request – the request for which to obtain the response
- conn – the connection over which the request was sent
- context – the context for receiving the response
Throws: - IOException – in case of an I/O error.
- HttpException – in case of HTTP protocol violation or a processing
problem.
Returns: the terminal response, not yet post-processed
/**
* Waits for and receives a response.
* This method will automatically ignore intermediate responses
* with status code 1xx.
*
* @param request the request for which to obtain the response
* @param conn the connection over which the request was sent
* @param context the context for receiving the response
*
* @return the terminal response, not yet post-processed
*
* @throws IOException in case of an I/O error.
* @throws HttpException in case of HTTP protocol violation or a processing
* problem.
*/
protected HttpResponse doReceiveResponse(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws HttpException, IOException {
Args.notNull(request, "HTTP request");
Args.notNull(conn, "Client connection");
Args.notNull(context, "HTTP context");
HttpResponse response = null;
int statusCode = 0;
while (response == null || statusCode < HttpStatus.SC_OK) {
response = conn.receiveResponseHeader();
statusCode = response.getStatusLine().getStatusCode();
if (statusCode < HttpStatus.SC_CONTINUE) {
throw new ProtocolException("Invalid response: " + response.getStatusLine());
}
if (canResponseHaveBody(request, response)) {
conn.receiveResponseEntity(response);
}
} // while intermediate response
return response;
}
Post-processes the given response using the given protocol processor and
completes the process of request execution.
This method does not read the response entity, if any. The connection over which content of the response entity is being streamed from cannot be reused until EntityUtils.consume(HttpEntity)
has been invoked.
Params: - response – the response object to post-process
- processor – the processor to use
- context – the context for post-processing the response
Throws: - IOException – in case of an I/O error.
- HttpException – in case of HTTP protocol violation or a processing
problem.
/**
* Post-processes the given response using the given protocol processor and
* completes the process of request execution.
* <p>
* This method does <i>not</i> read the response entity, if any.
* The connection over which content of the response entity is being
* streamed from cannot be reused until
* {@link org.apache.http.util.EntityUtils#consume(org.apache.http.HttpEntity)}
* has been invoked.
*
* @param response the response object to post-process
* @param processor the processor to use
* @param context the context for post-processing the response
*
* @throws IOException in case of an I/O error.
* @throws HttpException in case of HTTP protocol violation or a processing
* problem.
*/
public void postProcess(
final HttpResponse response,
final HttpProcessor processor,
final HttpContext context) throws HttpException, IOException {
Args.notNull(response, "HTTP response");
Args.notNull(processor, "HTTP processor");
Args.notNull(context, "HTTP context");
context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
processor.process(response, context);
}
} // class HttpRequestExecutor