HttpResponseHandler<AmazonWebServiceResponse<T>> responseHandler,
HttpResponseHandler<AmazonServiceException> errorResponseHandler)
throws AmazonServiceException {
URI endpoint = request.getEndpoint();
HttpMethodBase method = createHttpMethodFromRequest(request);
/* Set content type and encoding */
if (method.getRequestHeader("Content-Type") == null) {
log.debug("Setting content-type to application/x-www-form-urlencoded; " +
"charset=" + DEFAULT_ENCODING.toLowerCase());
method.addRequestHeader("Content-Type",
"application/x-www-form-urlencoded; " +
"charset=" + DEFAULT_ENCODING.toLowerCase());
} else {
log.debug("Not overwriting Content-Type; already set to: " + method.getRequestHeader("Content-Type"));
}
/*
* Depending on which response handler we end up choosing to handle the
* HTTP response, it might require us to leave the underlying HTTP
* connection open, depending on whether or not it reads the complete
* HTTP response stream from the HTTP connection, or if delays reading
* any of the content until after a response is returned to the caller.
*/
boolean leaveHttpConnectionOpen = false;
/*
* Apache HttpClient omits the port number in the Host header (even if
* we explicitly specify it) if it's the default port for the protocol
* in use. To ensure that we use the same Host header in the request and
* in the calculated string to sign (even if Apache HttpClient changed
* and started honoring our explicit host with endpoint), we follow this
* same behavior here and in the QueryString signer.
*/
String hostHeader = endpoint.getHost();
if (HttpUtils.isUsingNonDefaultPort(endpoint)) {
hostHeader += ":" + endpoint.getPort();
}
method.addRequestHeader("Host", hostHeader);
// When we release connections, the connection manager leaves them
// open so they can be reused. We want to close out any idle
// connections so that they don't sit around in CLOSE_WAIT.
httpClient.getHttpConnectionManager().closeIdleConnections(1000 * 30);
int retries = 0;
while (true) {
try {
requestLog.info("Sending Request: " + request.toString());
retries++;
int status = httpClient.executeMethod(method);
if (isRequestSuccessful(status)) {
/*
* If we get back any 2xx status code, then we know we should
* treat the service call as successful.
*/
leaveHttpConnectionOpen = responseHandler.needsConnectionLeftOpen();
return handleResponse(request, responseHandler, method);
} else if (isTemporaryRedirect(method, status)) {
/*
* S3 sends 307 Temporary Redirects if you try to delete an
* EU bucket from the US endpoint. If we get a 307, we'll
* point the HTTP method to the redirected location, and let
* the next retry deliver the request to the right location.
*/
Header locationHeader = method.getResponseHeader("location");
String redirectedLocation = locationHeader.getValue();
log.debug("Redirecting to: " + redirectedLocation);
method.setURI(new org.apache.commons.httpclient.URI(redirectedLocation, false));
} else {
leaveHttpConnectionOpen = errorResponseHandler.needsConnectionLeftOpen();
AmazonServiceException exception = handleErrorResponse(request, errorResponseHandler, method);
if (shouldRetry(exception, retries)) {
requestLog.info("Received retryable error response: " + status + ", retrying request...");
pauseExponentially(retries);
} else {
throw exception;
}
}
} catch (IOException ioe) {
log.error("Unable to execute HTTP request: " + ioe.getMessage());
throw new AmazonClientException("Unable to execute HTTP request: "
+ ioe.getMessage(), ioe);
} finally {
/*
* Some response handlers need to manually manage the HTTP
* connection and will take care of releasing the connection on
* their own, but if this response handler doesn't need the
* connection left open, we go ahead and release the it to free
* up resources.
*/
if (!leaveHttpConnectionOpen) {
try {method.getResponseBodyAsStream().close();} catch (Throwable t) {}
method.releaseConnection();
}
}
}
}