HttpClient 연결 풀 및 재 시도 메커니즘
110925 단어 HttpClient
2.1 HttpClient 버 전
필자 가 사용 한 버 전 은 4.5.5 이 고 Maven 프로젝트 이기 때문에 pom 파일 에 해당 하 는 좌 표를 도입 해 야 합 니 다.
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
2.2 프로젝트 에 사용 되 는 도구 류 는 다음 과 같다.
package cn.htjc.customer.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
public class HttpClientUtil {
// utf-8
private static final String CHARSET_UTF_8 = "utf-8";
// HTTP 。 form ,
private static final String CONTENT_TYPE_FORM_URL = "application/x-www-form-urlencoded";
//
private static PoolingHttpClientConnectionManager pool;
//
private static RequestConfig requestConfig;
static {
try {
log.info(" HttpClient...... ");
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());
// HTTP HTPPS
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslsf).build();
//
pool = new PoolingHttpClientConnectionManager(
socketFactoryRegistry);
//
pool.setMaxTotal(200);
//
pool.setDefaultMaxPerRoute(20);
// requestConfig
// timeout
int socketTimeout = 1000;
// timeout
int connectTimeout = 10000;
// timeout
int connectionRequestTimeout = 10000;
//
requestConfig = RequestConfig.custom().setConnectionRequestTimeout(
connectionRequestTimeout).setSocketTimeout(socketTimeout).setConnectTimeout(
connectTimeout).build();
log.info(" HttpClient...... ");
} catch (Exception e) {
log.error(" HttpClient...... ");
}
}
private HttpClientUtil() {
}
private static CloseableHttpClient getHttpClient() {
// 503 ,
ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy = new ServiceUnavailableRetryStrategy() {
@Override
public boolean retryRequest(HttpResponse httpResponse, int i, HttpContext httpContext) {
if (i < 3) {
log.info("ServiceUnavailableRetryStrategy========================"+i);
return true;
}
return false;
}
@Override
public long getRetryInterval() {
return 2000L;
}
};
CloseableHttpClient httpClient = HttpClients.custom()
//
.setConnectionManager(pool)
//
.setDefaultRequestConfig(requestConfig)
//
.setRetryHandler(new DefaultHttpRequestRetryHandler())
.setServiceUnavailableRetryStrategy(serviceUnavailableRetryStrategy)
.build();
return httpClient;
}
public static String doGet(String url, Map<String, String> param) {
// Httpclient
CloseableHttpClient httpClient = getHttpClient();
String resultString = "";
CloseableHttpResponse response = null;
try {
// uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// http GET
HttpGet httpGet = new HttpGet(uri);
//
response = httpClient.execute(httpGet);
// 200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPost(String url, Map<String, String> param) {
// Httpclient
CloseableHttpClient httpClient = getHttpClient();
CloseableHttpResponse response = null;
String resultString = "";
try {
// Http Post
HttpPost httpPost = new HttpPost(url);
//
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
//
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, CHARSET_UTF_8);
entity.setContentType(CONTENT_TYPE_FORM_URL);
httpPost.setEntity(entity);
}
// http main
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url) {
return doPost(url, null);
}
public static String doPostJson(String url, String json) {
// Httpclient
CloseableHttpClient httpClient = getHttpClient();
CloseableHttpResponse response = null;
String resultString = "";
try {
// Http Post
HttpPost httpPost = new HttpPost(url);
//
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// http
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), CHARSET_UTF_8);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
}
코드 에 @ Slf4j 가 나 타 났 습 니 다. log 를 도입 하고 로 그 를 수 동 으로 인쇄 하 는 역할 을 합 니 다.이 주 해 는 lombok 의 주해 입 니 다.Route 가 무엇 인지 설명해 주세요.Route 의 개념 은 클 라 이언 트 기기 에서 타 겟 기기 까지 의 노선 으로 이해 할 수 있다. 예 를 들 어 HttpClient 의 실현 을 이용 하여 각각 www. 163. com 의 자원 과 www. sina. com 의 자원 을 요청 하면 두 개의 route 가 생 긴 다.결 성 된 조건 에서 모든 Route 에 대해 HttpClient 는 2 개의 연결 만 유지 하고 총 20 개의 연결 을 초과 하지 않 습 니 다.
2. 필 자 는 http 연결 풀 을 다시 말 합 니 다.
(3) HttpClient 의 재 시도 메커니즘
위 에서 이렇게 많은 말 을 한 것 은 바로 아래 의 재시험 문 제 를 끌 어 내기 위해 서 이다.프로젝트 에서 외부 인터페이스 에 접근 하려 고 하기 때문에 인터페이스 에 접근 할 때 가끔 SocketTimeOutException: Read timed out 이 나타 납 니 다. 사실은 클 라 이언 트 가 서버 의 데 이 터 를 읽 는 데 시간 이 초과 되 었 습 니 다.
3.1. 그럼 문제 가 생 겼 습 니 다. HttpClient 재 시도 전략 이 있 습 니까?
PoolingHttpClient ConnectionManager 를 사용 하여 얻 은 InternalHttpClient 인 스 턴 스 는 추상 적 인 CloseableHttpClient 의 실현 입 니 다.
Client ExecChain 인터페이스의 실현 클래스 를 살 펴 보고 build () 방법 을 간단하게 살 펴 보 겠 습 니 다.
public CloseableHttpClient build() {
//
// MainClientExec
ClientExecChain execChain = this.createMainExec(requestExecCopy, (HttpClientConnectionManager)connManagerCopy, (ConnectionReuseStrategy)reuseStrategyCopy, (ConnectionKeepAliveStrategy)keepAliveStrategyCopy, new ImmutableHttpProcessor(new HttpRequestInterceptor[]{new RequestTargetHost(), new RequestUserAgent(userAgentCopy)}), (AuthenticationStrategy)targetAuthStrategyCopy, (AuthenticationStrategy)proxyAuthStrategyCopy, (UserTokenHandler)userTokenHandlerCopy);
execChain = this.decorateMainExec(execChain);
// ProtocolExec
ClientExecChain execChain = new ProtocolExec(execChain, httpprocessorCopy);
ClientExecChain execChain = this.decorateProtocolExec(execChain);
// Add request retry executor, if not disabled
if (!automaticRetriesDisabled) {
HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
if (retryHandlerCopy == null) {
retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
}
execChain = new RetryExec(execChain, retryHandlerCopy);
}
//
// , ServiceUnavailableRetryExec
ServiceUnavailableRetryStrategy serviceUnavailStrategyCopy = this.serviceUnavailStrategy;
if (serviceUnavailStrategyCopy != null) {
execChain = new ServiceUnavailableRetryExec((ClientExecChain)execChain, serviceUnavailStrategyCopy);
}
// RedirectExec
if (!this.redirectHandlingDisabled) {
authSchemeRegistryCopy = this.redirectStrategy;
if (authSchemeRegistryCopy == null) {
authSchemeRegistryCopy = DefaultRedirectStrategy.INSTANCE;
}
execChain = new RedirectExec((ClientExecChain)execChain, (HttpRoutePlanner)routePlannerCopy, (RedirectStrategy)authSchemeRegistryCopy);
}
//
return new InternalHttpClient((ClientExecChain)execChain, (HttpClientConnectionManager)connManagerCopy, (HttpRoutePlanner)routePlannerCopy, cookieSpecRegistryCopy, (Lookup)authSchemeRegistryCopy, (CookieStore)defaultCookieStore, (CredentialsProvider)defaultCredentialsProvider, this.defaultRequestConfig != null ? this.defaultRequestConfig : RequestConfig.DEFAULT, closeablesCopy);
}
위 에서 아래로 서로 다른 Client ExecChain 인 스 턴 스 를 만 들 었 습 니 다.메모: 대상 을 만 드 는 순 서 는 실행 기 체인 의 순서 입 니 다.
CloseableHttpClient 인 스 턴 스 를 구성 할 때 자동 재 시도 기능 을 닫 았 는 지 여 부 를 판단 합 니 다. automaticRetriesDisabled 는 기본적으로 false 입 니 다.실행 체인 이 지정 되 지 않 으 면 RetryExec 를 사용 합 니 다.기본 재 시도 정책 은 DefaultHttpRequestRetry Handler 입 니 다.
ServiceUnavailable Retry Strategy 인 터 페 이 스 를 다시 쓰 거나 Default ServiceUnavailable Retry Strategy 를 사용 하면 ServiceUnavailable Retry Exec 도 실행 기 체인 에 가입 합 니 다.
마찬가지 로 redirectHandling Disabled 는 기본적으로 false 이 고 RedirectExec 도 실행 기 체인 에 가입 하여 가장 먼저 실 행 됩 니 다.
3.2 실행 절차
앞에서 우리 가 사용 하 는 Htticlient 는 본질 적 으로 InternalhttpClient 인 것 을 보 았 습 니 다. 여기 서 그의 실행 이 데 이 터 를 보 내 는 방법 을 보 겠 습 니 다.
@Override
protected CloseableHttpResponse doExecute(
final HttpHost target,
final HttpRequest request,
final HttpContext context) throws IOException, ClientProtocolException {
//
return this.execChain.execute(route, wrapper, localcontext, execAware);
}
}
먼저 RedirectExec 를 거 쳐 RedirectExec 에서 ServiceUnavailableRetryExec 의 excute () 를 호출 하고 ServiceUnavailableRetryExec 에 들 어간 후 RetryExec 의 excute () 를 호출 하 며 RetryExec 에 들 어간 후 ProtocolExec 의 execute () 를 호출 하고 마지막 으로 MainClient Exec 의 excute () 를 호출 합 니 다.
실행 기 체인 이 끝 난 후 HttpRequestExecutor 의 excute (), excute () 방법 을 실행 하여 자신의 doSendRequest () 를 호출 하 였 습 니 다.
이후 한 걸음 한 걸음 돌아 와 이상 을 만 나 처리 했다.
다음은 Retry Exec 에서 요청 한 부분 입 니 다.
public CloseableHttpResponse execute(HttpRoute route,
HttpRequestWrapper request,
HttpClientContext context,
HttpExecutionAware execAware) throws IOException, HttpException {
//
Args.notNull(route, "HTTP route");
Args.notNull(request, "HTTP request");
Args.notNull(context, "HTTP context");
//
Header[] origheaders = request.getAllHeaders();
// 1
int execCount = 1;
while(true) {
try {
// executor http
return this.requestExecutor.execute(route, request, context, execAware);
} catch (IOException var9) {
// IO , ,
if (execAware != null && execAware.isAborted()) {
this.log.debug("Request has been aborted");
throw var9;
}
// , ,
if (!this.retryHandler.retryRequest(var9, execCount, context)) {
if (var9 instanceof NoHttpResponseException) {
NoHttpResponseException updatedex = new NoHttpResponseException(route.getTargetHost().toHostString() + " failed to respond");
updatedex.setStackTrace(var9.getStackTrace());
throw updatedex;
}
throw var9;
}
//
if (this.log.isInfoEnabled()) {
this.log.info("I/O exception (" + var9.getClass().getName() + ") caught when processing request to " + route + ": " + var9.getMessage());
}
//
if (this.log.isDebugEnabled()) {
this.log.debug(var9.getMessage(), var9);
}
//
if (!RequestEntityProxy.isRepeatable(request)) {
this.log.debug("Cannot retry non-repeatable request");
throw new NonRepeatableRequestException("Cannot retry request with a non-repeatable request entity", var9);
}
//
request.setHeaders(origheaders);
//
if (this.log.isInfoEnabled()) {
this.log.info("Retrying request to " + route);
}
++execCount;
}
}
}
IOException 이 발생 하면 다시 시도 할 지 여 부 를 판단 합 니 다.재 시도 하면 해당 횟수 를 기록 하고 재 시도 하지 않 으 면 이상 을 던 지고 물러난다.
// final ,
public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler();
//
private final int retryCount;
// ,
private final boolean requestSentRetryEnabled;
//
private final Set<Class<? extends IOException>> nonRetriableClasses;
// 3 , ,
public DefaultHttpRequestRetryHandler() {
this(3, false);
}
public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) {
this(retryCount, requestSentRetryEnabled, Arrays.asList(
InterruptedIOException.class,
UnknownHostException.class,
ConnectException.class,
SSLException.class));
}
protected DefaultHttpRequestRetryHandler(
final int retryCount,
final boolean requestSentRetryEnabled,
final Collection<Class<? extends IOException>> clazzes) {
super();
this.retryCount = retryCount;
this.requestSentRetryEnabled = requestSentRetryEnabled;
this.nonRetriableClasses = new HashSet<Class<? extends IOException>>();
for (final Class<? extends IOException> clazz: clazzes) {
this.nonRetriableClasses.add(clazz);
}
}
구조 함 수 를 통 해 알 수 있 듯 이:
public boolean retryRequest(IOException exception,
int executionCount,
HttpContext context) {
//
Args.notNull(exception, "Exception parameter");
Args.notNull(context, "HTTP context");
// ,
if (executionCount > this.retryCount) {
return false;
// 4 ,
} else if (this.nonRetriableClasses.contains(exception.getClass())) {
return false;
} else {
Iterator i$ = this.nonRetriableClasses.iterator();
Class rejectException;
do {
if (!i$.hasNext()) {
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpRequest request = clientContext.getRequest();
//
if (this.requestIsAborted(request)) {
return false;
}
// ,get ,post/put
if (this.handleAsIdempotent(request)) {
return true;
}
// ,
if (clientContext.isRequestSent() && !this.requestSentRetryEnabled) {
return false;
}
return true;
}
rejectException = (Class)i$.next();
} while(!rejectException.isInstance(exception)); /// ,
return false;
}
}
기본 재 시도 정책
public static final String HTTP_REQ_SENT = "http.request_sent";
public boolean isRequestSent() {
final Boolean b = getAttribute(HTTP_REQ_SENT, Boolean.class);
return b != null && b.booleanValue();
}
현재 http Context 의 http. requestsent 를 true 로 설정 하면 발송 에 성공 했다 고 생각 합 니 다.
HttpRequestExecutor 의 excute () 는 자신의 doSendRequest () 를 호출 합 니 다.
protected HttpResponse doSendRequest(HttpRequest request,
HttpClientConnection conn,
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("http.connection", conn);
// , http.request_sent context , false
context.setAttribute("http.request_sent", Boolean.FALSE);
// request header
conn.sendRequestHeader(request);
// post/put body ,
if (request instanceof HttpEntityEnclosingRequest) {
boolean sendentity = true;
// http
ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
// 100-continue, http 1.0
if (((HttpEntityEnclosingRequest)request).expectContinue() && !ver.lessEquals(HttpVersion.HTTP_1_0)) {
// ,
conn.flush();
// Checks if response data is available from the connection
if (conn.isResponseAvailable(this.waitForContinue)) {
// Receives the request line and headers of the next response available from this connection.
response = conn.receiveResponseHeader();
// ( body)
if (this.canResponseHaveBody(request, response)) {
// Receives the next response entity available from this connection and attaches it to an existing HttpResponse object.
conn.receiveResponseEntity(response);
}
//
int status = response.getStatusLine().getStatusCode();
if (status < 200) {
if (status != 100) {
throw new ProtocolException("Unexpected response: " + response.getStatusLine());
}
response = null;
} else {
sendentity = false;
}
}
}
if (sendentity) {
//
conn.sendRequestEntity((HttpEntityEnclosingRequest)request);
}
}
// Writes out all pending buffered data over the open connection.
conn.flush();
// http.request_sent true
context.setAttribute("http.request_sent", Boolean.TRUE);
return response;
}
실체 소지 여 부 를 판단 하 는 방법
protected boolean canResponseHaveBody(HttpRequest request, HttpResponse response) {
// head , false HEAD:
if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
return false;
} else {
int status = response.getStatusLine().getStatusCode();
return status >= 200 && status != 204 && status != 304 && status != 205;
}
}
주: HttpEntity Enclosing Request 는 인터페이스 입 니 다.
public interface HttpEntityEnclosingRequest extends HttpRequest {
// Server
boolean expectContinue();
// httpEntity
void setEntity(HttpEntity entity);
// httpEntity
HttpEntity getEntity();
}
HttpEntityEnclosing RequestBase 는 HttpEntityEnclosing Request 를 실현 하 는 추상 적 인 클래스 입 니 다.
public abstract class HttpEntityEnclosingRequestBase extends HttpRequestBase implements HttpEntityEnclosingRequest {
// HttpEntity , http , , StringEntity
private HttpEntity entity;
public HttpEntityEnclosingRequestBase() {
}
public HttpEntity getEntity() {
return this.entity;
}
public void setEntity(HttpEntity entity) {
this.entity = entity;
}
// expect-continue
public boolean expectContinue() {
// Except
Header expect = this.getFirstHeader("Expect");
// except , 100-continue true
return expect != null && "100-continue".equalsIgnoreCase(expect.getValue());
}
public Object clone() throws CloneNotSupportedException {
HttpEntityEnclosingRequestBase clone = (HttpEntityEnclosingRequestBase)super.clone();
if (this.entity != null) {
clone.entity = (HttpEntity)CloneUtils.cloneObject(this.entity);
}
return clone;
}
}
아래 그림 에서 알 수 있 듯 이 HttpPost 와 HttpPut 는 HttpEntity Enclosing RequestBase 의 하위 클래스 로 상기 조작 과정 을 간략하게 분석 한 것 이다.
3.3 닫 고 다시 시도 하기
기본적으로 재 시 도 를 시작 합 니 다. HttpClient Builder 를 만 들 때 아래 방법 으로 닫 을 수 있 습 니 다.
public final HttpClientBuilder disableAutomaticRetries() {
this.automaticRetriesDisabled = true;
return this;
}
(4) 총화
4.1 재 시도 발생 조건
또한, 우 리 는 두 가지 시간 초과, 연결 시간 초과 와 읽 기 시간 초과: 1. java. net. SocketTimeoutException: Read timed out 2. java. net. SocketTimeoutException: connect timed out 두 가지 시간 초 과 는 모두 SocketTimeoutException 이 며, Interrupted IO Exception 을 계승 하여 스 레 드 중단 이상 에 속 하 며, 재 시도 하지 않 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
【httpclient】에서의 요구로부터 controller까지의 흐름에 대해서 확인과 리팩토링이전에는 JQuery의 autocomplete, ajax 및 httpclient를 사용하여 자동 완성을 구현했지만 내용에 대해 희미하게만 파악할 수 없었습니다. 리팩토링을 실시하면서 내용을 확인한다. 우선, 외부 A...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.