当前位置: 首页>编程语言>正文

【RestClient】

官网

Apache HttpComponents – Apache HttpComponents

依赖

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents.client5</groupId>
            <artifactId>httpclient5</artifactId>
            <version>5.1.3</version>
        </dependency>
    </dependencies>

源码

HttpClient类图

【RestClient】,第1张
InternalHttpClient.png

execute流程

InternalHttpClient

protected CloseableHttpResponse doExecute(
            final HttpHost target,
            final ClassicHttpRequest request,
            final HttpContext context) throws IOException {
        Args.notNull(request, "HTTP request");
        try {
            // 上下文
            final HttpClientContext localcontext = HttpClientContext.adapt(
                    context != null context : new BasicHttpContext());
            // 请求超时、连接超时、响应超时等
            RequestConfig config = null;
            if (request instanceof Configurable) {
                config = ((Configurable) request).getConfig();
            }
            if (config != null) {
                localcontext.setRequestConfig(config);
            }
            setupContext(localcontext);
            final HttpRoute route = determineRoute(
                    target != null target : RoutingSupport.determineHost(request),
                    localcontext);
            final String exchangeId = ExecSupport.getNextExchangeId();
            localcontext.setExchangeId(exchangeId);
            if (LOG.isDebugEnabled()) {
                LOG.debug("{} preparing request execution", exchangeId);
            }

            final ExecRuntime execRuntime = new InternalExecRuntime(LOG, connManager, requestExecutor,
                    request instanceof CancellableDependency (CancellableDependency) request : null);
            final ExecChain.Scope scope = new ExecChain.Scope(exchangeId, route, request, execRuntime, localcontext);
            // 几个ExecChainElement连接成处理链
            // 实际处理是ExecChainHandler
            final ClassicHttpResponse response = this.execChain.execute(ClassicRequestBuilder.copy(request).build(), scope);
            return CloseableHttpResponse.adapt(response);
        } catch (final HttpException httpException) {
            throw new ClientProtocolException(httpException.getMessage(), httpException);
        }
    }

HttpRequestRetryExec

public class HttpRequestRetryExec implements ExecChainHandler {

    private static final Logger LOG = LoggerFactory.getLogger(HttpRequestRetryExec.class);

    private final HttpRequestRetryStrategy retryStrategy;

    public HttpRequestRetryExec(
            final HttpRequestRetryStrategy retryStrategy) {
         Args.notNull(retryStrategy, "retryStrategy");
         this.retryStrategy = retryStrategy;
    }

    @Override
    public ClassicHttpResponse execute(
            final ClassicHttpRequest request,
            final Scope scope,
            final ExecChain chain) throws IOException, HttpException {
        Args.notNull(request, "request");
        Args.notNull(scope, "scope");
        final String exchangeId = scope.exchangeId;
        final HttpRoute route = scope.route;
        final HttpClientContext context = scope.clientContext;
        ClassicHttpRequest currentRequest = request;
        // 这个版本从1开始计数
        for (int execCount = 1;; execCount++) {
            final ClassicHttpResponse response;
            try {
                 response = chain.proceed(currentRequest, scope);
            } catch (final IOException ex) {
                if (scope.execRuntime.isExecutionAborted()) {
                    throw new RequestFailedException("Request aborted");
                }
                // 返回的是null
                final HttpEntity requestEntity = request.getEntity();
                if (requestEntity != null && !requestEntity.isRepeatable()) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} cannot retry non-repeatable request", exchangeId);
                    }
                    throw ex;
                }
                // IO异常时的retry判断逻辑
                if (retryStrategy.retryRequest(request, ex, execCount, context)) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} {}", exchangeId, ex.getMessage(), ex);
                    }
                    if (LOG.isInfoEnabled()) {
                        LOG.info("Recoverable I/O exception ({}) caught when processing request to {}",
                                ex.getClass().getName(), route);
                    }
                    currentRequest = ClassicRequestBuilder.copy(scope.originalRequest).build();
                    continue;
                } else {
                    if (ex instanceof NoHttpResponseException) {
                        final NoHttpResponseException updatedex = new NoHttpResponseException(
                                route.getTargetHost().toHostString() + " failed to respond");
                        updatedex.setStackTrace(ex.getStackTrace());
                        throw updatedex;
                    }
                    throw ex;
                }
            }

            try {
                final HttpEntity entity = request.getEntity();
                if (entity != null && !entity.isRepeatable()) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} cannot retry non-repeatable request", exchangeId);
                    }
                    return response;
                }
                // 从服务端获得响应时判断是否retry,如响应429限频
                // 4.5版本只会进行IO异常重试 
                if (retryStrategy.retryRequest(response, execCount, context)) {
                    // 重试间隔时间需要小于响应时间
                    final TimeValue nextInterval = retryStrategy.getRetryInterval(response, execCount, context);
                    // Make sure the retry interval does not exceed the response timeout
                    if (TimeValue.isPositive(nextInterval)) {
                        final RequestConfig requestConfig = context.getRequestConfig();
                        final Timeout responseTimeout = requestConfig.getResponseTimeout();
                        if (responseTimeout != null && nextInterval.compareTo(responseTimeout) > 0) {
                            return response;
                        }
                    }
                    response.close();
                    if (TimeValue.isPositive(nextInterval)) {
                        try {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("{} wait for {}", exchangeId, nextInterval);
                            }
                            nextInterval.sleep();
                        } catch (final InterruptedException e) {
                            Thread.currentThread().interrupt();
                            throw new InterruptedIOException();
                        }
                    }
                    currentRequest = ClassicRequestBuilder.copy(scope.originalRequest).build();
                } else {
                    return response;
                }
            } catch (final RuntimeException ex) {
                response.close();
                throw ex;
            }
        }
    }

}


HttpRequestRetryStrategy

public interface HttpRequestRetryStrategy {

    /**
     * Determines if a method should be retried after an I/O exception
     * occurred during execution.
     *
     * @param request the request failed due to an I/O exception
     * @param exception the exception that occurred
     * @param execCount the number of times this method has been
     *                  unsuccessfully executed
     * @param context the context for the request execution
     *
     * @return {@code true} if the request should be retried, {@code false}
     *         otherwise
     */
    boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context);

    /**
     * Determines if a method should be retried given the response from
     * the target server.
     *
     * @param response the response from the target server
     * @param execCount the number of times this method has been
     *                  unsuccessfully executed
     * @param context the context for the request execution
     *
     * @return {@code true} if the request should be retried, {@code false}
     *         otherwise
     */
    boolean retryRequest(HttpResponse response, int execCount, HttpContext context);

    /**
     * Determines the retry interval between subsequent retries.
     *
     * @param response the response from the target server
     * @param execCount the number of times this method has been
     *                  unsuccessfully executed
     * @param context the context for the request execution
     *
     * @return the retry interval between subsequent retries
     */
    TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context);

默认的实现中 interval 需要小于响应超时时间

请求时可能发生IO异常,如连接超时或者响应超时
如果从服务端正确获得响应,不同的响应码代表不同场景
上述两种大的场景都需要考虑重试机制,
①如连接超时设置的比较长,又重试多次可能影响任务执行的时间引起其他问题
②响应码429,得考虑sleep一定时间再重试
③401等即使重试大概率没用

案例

package com.itachi.http;


import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class Test {

    @org.junit.Test
    public void testHttp() {
        HttpClientBuilder custom = HttpClients.custom();

        // 连接池设置
        // SocketFactory + 最大连接数 + 每个rout的最大连接数
        // 连接池中的连接默认一直存活
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(testSSL());
        Registry<ConnectionSocketFactory> ssl = RegistryBuilder.<ConnectionSocketFactory>create()
                .register(URIScheme.HTTP.id, PlainConnectionSocketFactory.getSocketFactory())
                .register(URIScheme.HTTPS.id, sslConnectionSocketFactory)
                .build();
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(ssl);
        custom.setConnectionManager(poolingHttpClientConnectionManager);
        poolingHttpClientConnectionManager.setMaxTotal(200);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(50);

        // IO异常和收到请求后重试逻辑
        DefaultHttpRequestRetryStrategy defaultHttpRequestRetryStrategy = new DefaultHttpRequestRetryStrategy(1, TimeValue.ofSeconds(1));
        custom.setRetryStrategy(defaultHttpRequestRetryStrategy);

        //
        custom.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE);

        // 请求参数:连接超时、响应超时
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(Timeout.ofSeconds(30))
                .setConnectionKeepAlive(TimeValue.ofSeconds(30))
                .setResponseTimeout(Timeout.ofSeconds(10)).build();
        custom.setDefaultRequestConfig(requestConfig);

        CloseableHttpClient client = custom.build();

        HttpGet httpGet = new HttpGet("https://www.baidu.com");
        try {
            CloseableHttpResponse execute = client.execute(httpGet);
            System.out.println(execute.getCode());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public SSLContext testSSL() {
        try {
            SSLContext tls = SSLContext.getInstance("TLS");
            TrustManager[] trust = {new X509TrustManager() {
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                }

                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                }

                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }};
            tls.init(null, trust, null);
            return tls;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return null;
    }
}


https://www.xamrdz.com/lan/5jy2016518.html

相关文章: