com.kingsoft.services.http.KWSHttpClient Maven / Gradle / Ivy
package com.kingsoft.services.http;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpStatus;
import org.apache.http.annotation.ThreadSafe;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.protocol.BasicHttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.kingsoft.services.ClientOptions;
import com.kingsoft.services.HttpHeaders;
import com.kingsoft.services.RequestMessage;
import com.kingsoft.services.ResponseMessage;
import com.kingsoft.services.auth.Signer;
import com.kingsoft.services.exception.KWSClientException;
import com.kingsoft.services.exception.KWSServiceException;
import com.kingsoft.services.exception.KWSTimeoutException;
import com.kingsoft.services.exception.ServiceUnavailableException;
import com.kingsoft.services.handlers.HttpResponseHandler;
import com.kingsoft.services.retry.RetryStrategy;
@ThreadSafe
public class KWSHttpClient implements IKWSHttpClient {
final static Logger log = LoggerFactory.getLogger(KWSHttpClient.class);
private final ClientOptions options;
/** Internal client for sending HTTP requests */
private final HttpClient httpClient;
private static final HttpRequestFactory httpRequestFactory = new HttpRequestFactory();
private static final HttpClientFactory httpClientFactory = new HttpClientFactory();
public KWSHttpClient(ClientOptions options) {
this(options, httpClientFactory.createHttpClient(options));
}
public KWSHttpClient(ClientOptions options, int timeout) {
this(options, httpClientFactory.createHttpClient(options, timeout));
}
protected KWSHttpClient(ClientOptions options, HttpClient httpClient) {
this.options = options;
this.httpClient = httpClient;
}
@Override
public ResponseMessage execute(RequestMessage> requestMsg,
HttpResponseHandler responseHandler,
HttpResponseHandler errorResponseHandler)
throws KWSServiceException, KWSClientException {
final ExecOneRequestParams params = new ExecOneRequestParams();
while (true) {
params.initPerRetry();
// back off if needed
if (params.retriedException != null) {
pauseBeforeNextRetry(requestMsg, params.retriedException,
params.tryTimes, options.getRetryStrategy());
}
try {
ResponseMessage response = executeOneRequest(requestMsg,
responseHandler, errorResponseHandler, params);
if (response != null) {
return response;
}
if (!shouldRetry(requestMsg, params.apacheRequest,
params.retriedException, params.tryTimes,
options.getRetryStrategy())) {
assert (params.retriedException != null);
log.error(params.retriedException.getMessage(),
params.retriedException);
throw params.retriedException;
}
} catch (IOException e) {
KWSClientException exp = new ServiceUnavailableException(requestMsg.getRequestId(),
"Unable to execute HTTP request: " + e.getMessage(), e);
if (!shouldRetry(requestMsg, params.apacheRequest, exp,
params.tryTimes, options.getRetryStrategy())) {
log.error(exp.getMessage(), exp);
throw exp;
}
params.retriedException = exp;
} finally {
if (params.apacheResponse != null) {
HttpEntity entity = params.apacheResponse.getEntity();
if (entity != null) {
try {
if (entity.getContent() != null) {
entity.getContent().close();
}
} catch (IOException e) {
log.error("Cannot close the response content." + e.getMessage(),
e);
}
}
}
}
}
}
@SuppressWarnings("deprecation")
@Override
public void shutdown() {
IdleConnectionReaper.removeConnectionManager(httpClient
.getConnectionManager());
httpClient.getConnectionManager().shutdown();
}
/**
* Stateful parameters that are used for executing a single http request.
*/
private static class ExecOneRequestParams {
int tryTimes; // monotonic increasing
KWSClientException retriedException; // last retryable exception
HttpRequestBase apacheRequest;
org.apache.http.HttpResponse apacheResponse;
public int getTryTimes() {
return tryTimes;
}
boolean isRetry() {
return tryTimes > 1;
}
void initPerRetry() {
tryTimes++;
apacheRequest = null;
apacheResponse = null;
}
void resetBeforeHttpRequest() {
retriedException = null;
}
}
/**
* Creates and initializes an HttpResponse object suitable to be passed to an
* HTTP response handler object.
*
* @param method
* The HTTP method that was invoked to get the response.
* @param request
* The HTTP request associated with the response.
*
* @return The new, initialized HttpResponse object ready to be passed to an
* HTTP response handler object.
*
* @throws IOException
* If there were any problems getting any response information from
* the HttpClient method object.
*/
private HttpResponse createResponse(HttpRequestBase method,
RequestMessage> request, org.apache.http.HttpResponse apacheHttpResponse)
throws IOException {
HttpResponse httpResponse = new HttpResponse(request, method);
if (apacheHttpResponse.getEntity() != null) {
httpResponse.setContent(apacheHttpResponse.getEntity().getContent());
}
httpResponse.setStatusCode(apacheHttpResponse.getStatusLine()
.getStatusCode());
httpResponse.setStatusText(apacheHttpResponse.getStatusLine()
.getReasonPhrase());
for (Header header : apacheHttpResponse.getAllHeaders()) {
httpResponse.addHeader(header.getName(), header.getValue());
}
if (apacheHttpResponse.containsHeader(HttpHeaders.RESPONSE_CODE)) {
httpResponse.setResponseCode(Integer.parseInt(apacheHttpResponse
.getFirstHeader(HttpHeaders.RESPONSE_CODE).getValue()));
} else {
throw new IOException(
String
.format(
"response has no %s header. StatusCode:%s, ReasonPhrase:%s, request id:%s",
HttpHeaders.RESPONSE_CODE, apacheHttpResponse.getStatusLine()
.getStatusCode(), apacheHttpResponse.getStatusLine()
.getReasonPhrase(), request.getRequestId()));
}
return httpResponse;
}
/**
* Returns the response from executing one http request; or null for retry.
*
* @throws IOException
*/
private ResponseMessage executeOneRequest(
final RequestMessage> request, HttpResponseHandler responseHandler,
HttpResponseHandler errorResponseHandler,
ExecOneRequestParams execParams) throws KWSServiceException, IOException {
if (execParams.isRetry()) {
// reset input stream for readable
InputStream requestInputStream = request.getContent();
if (requestInputStream != null) {
if (requestInputStream.markSupported()) {
requestInputStream.reset();
}
}
}
Signer.sign(request);
execParams.apacheRequest = httpRequestFactory.createHttpRequest(request,
options);
execParams.resetBeforeHttpRequest();
// ///////// Send HTTP request ////////////
long startTime = Calendar.getInstance().getTimeInMillis();
try {
execParams.apacheResponse = httpClient.execute(execParams.apacheRequest,
new BasicHttpContext());
} catch (IOException e) {
long endTime = Calendar.getInstance().getTimeInMillis();
if (log.isWarnEnabled()) {
log.warn(String
.format(
"http io error:%s, logid:%s, server:%s:%d, cmd:%s, try_times:%d, cost:%dms",
e.getMessage(), request.getRequestId(),
execParams.apacheRequest.getURI().getHost(),
execParams.apacheRequest.getURI().getPort(),
request.getResourcePath(), execParams.getTryTimes(), endTime
- startTime));
}
throw e;
}
HttpResponse httpResponse = createResponse(execParams.apacheRequest,
request, execParams.apacheResponse);
long ioEndTime = Calendar.getInstance().getTimeInMillis();
try {
if (isRequestSuccessful(execParams.apacheResponse)) {
try {
ResponseMessage response = new ResponseMessage();
response.setRequestId(request.getRequestId());
for (Header header : execParams.apacheResponse.getAllHeaders()) {
response.addMetadata(header.getName(), header.getValue());
}
T content = responseHandler.handle(httpResponse);
response.setContent(content);
return response;
} catch (KWSClientException e) {
execParams.retriedException = e;
return null;
}
}
final KWSServiceException kse = handleErrorResponse(request,
errorResponseHandler, execParams.apacheRequest, httpResponse,
execParams.apacheResponse);
execParams.retriedException = kse;
return null;
} finally {
long handleEndTime = Calendar.getInstance().getTimeInMillis();
if (log.isInfoEnabled()) {
log.info(String
.format(
"http io ok, logid:%s, cmd:%s, server:%s:%d, try_times:%d, code:%s(%d), msg:%s, io cost:%dms, handle response cost:%dms content size %d",
request.getRequestId(), request.getResourcePath(),
execParams.apacheRequest.getURI().getHost(),
execParams.apacheRequest.getURI().getPort(), execParams
.getTryTimes(), errorResponseHandler
.stringOfCode(httpResponse.getResponseCode()), httpResponse
.getStatusCode(), httpResponse.getStatusText(), ioEndTime
- startTime, handleEndTime - ioEndTime, execParams.apacheResponse.getEntity().getContentLength()));
}
}
}
/**
* Sleep for a period of time on failed request to avoid flooding a service
* with retries.
*
* @param originalRequest
* The original service request that is being executed.
* @param previousException
* Exception information for the previous attempt, if any.
* @param requestCount
* current request count (including the next attempt after the delay)
* @param retryPolicy
* The retry policy configured in this http client.
*/
private void pauseBeforeNextRetry(RequestMessage> originalRequest,
KWSClientException previousException, int requestCount,
RetryStrategy retryPolicy) throws KWSTimeoutException, KWSClientException {
final int retries = requestCount // including next attempt
- 1 // number of attempted requests
- 1; // number of attempted retries
long delay = retryPolicy.getBackoffStrategy().delayBeforeNextRetry(
originalRequest, previousException, retries);
log.debug(
"Retriable error detected, will retry in {}ms, attempt number:{}",
delay, retries);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new KWSClientException(originalRequest.getRequestId(), e.getMessage(), e);
}
}
/**
* Returns true if a failed request should be retried.
*
* @param request
* The original service request that is being executed.
* @param method
* The current HTTP method being executed.
* @param exception
* The client/service exception from the failed request.
* @param requestCount
* The number of times the current request has been attempted.
*
* @return True if the failed request should be retried.
*/
private boolean shouldRetry(RequestMessage> request,
HttpRequestBase method, KWSClientException exception, int requestCount,
RetryStrategy retryPolicy) {
final int retries = requestCount - 1;
int maxErrorRetry = options.getMaxRetryTimes();
// We should use the maxErrorRetry in
// the RetryPolicy if either the user has not explicitly set it in
// ClientConfiguration, or the RetryPolicy is configured to take
// higher precedence
if (maxErrorRetry < 0
|| !retryPolicy.isMaxErrorRetryInClientConfigHonored()) {
maxErrorRetry = retryPolicy.getMaxErrorRetry();
}
// Immediately fails when it has exceeds the max retry count.
if (retries >= maxErrorRetry)
return false;
// Never retry on requests containing non-repeatable entity
if (method instanceof HttpEntityEnclosingRequest) {
HttpEntity entity = ((HttpEntityEnclosingRequest) method).getEntity();
if (entity != null && !entity.isRepeatable()) {
log.debug("Entity not repeatable");
return false;
}
}
// Pass all the context information to the RetryCondition and let it
// decide whether it should be retried.
return retryPolicy.getRetryCondition().shouldRetry(request, exception,
retries);
}
private boolean isRequestSuccessful(org.apache.http.HttpResponse response) {
int status = response.getStatusLine().getStatusCode();
return status / 100 == HttpStatus.SC_OK / 100;
}
private KWSServiceException handleErrorResponse(RequestMessage> request,
HttpResponseHandler errorResponseHandler,
HttpRequestBase method, HttpResponse httpResponse,
final org.apache.http.HttpResponse apacheHttpResponse)
throws KWSClientException {
KWSServiceException exception = errorResponseHandler.handle(httpResponse);
return exception;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy