如何在 TunnelRefusedException 上重试 HttpClient 请求?

问题描述 投票:0回答:1

我希望 HttpClient 5 在遇到 TunnelRefusedException 时重试请求(代理拒绝初始连接是随机发生的,但重试时通常可以正常工作)。

我尝试扩展DefaultHttpRequestRetryStrategy,但它只能捕获继承自

IOException
的异常:

public class MyRetryStrategy extends DefaultHttpRequestRetryStrategy {

    public MyRetryStrategy() {
        super(2,
                TimeValue.ofSeconds(1L),
                Arrays.asList(InterruptedIOException.class, UnknownHostException.class, ConnectException.class, ConnectionClosedException.class, NoRouteToHostException.class, SSLException.class,
                        TunnelRefusedException.class  // not valid, because it is not an IOException !
                ),
                Arrays.asList(429, 503)
        );
    }
}

那么,也许

HttpRequestRetryStrategy
界面应该支持任何类型的
Exception
?或者也许
TunnelRefusedException
应该是
IOException

关于如何做到这一点有什么想法吗?

我想重试的错误的示例堆栈跟踪:

   org.apache.hc.client5.http.ClientProtocolException: CONNECT refused by proxy: HTTP/1.1 500 Internal Server Error
        at org.apache.hc.client5.http.impl.classic.InternalHttpClient.doExecute(InternalHttpClient.java:173)
        at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:245)
        at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:188)
[...]
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: org.apache.hc.client5.http.impl.TunnelRefusedException: CONNECT refused by proxy: HTTP/1.1 500 Internal Server Error
        at org.apache.hc.client5.http.impl.classic.ConnectExec.createTunnelToTarget(ConnectExec.java:284)
        at org.apache.hc.client5.http.impl.classic.ConnectExec.execute(ConnectExec.java:151)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.ProtocolExec.execute(ProtocolExec.java:192)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.HttpRequestRetryExec.execute(HttpRequestRetryExec.java:96)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.ContentCompressionExec.execute(ContentCompressionExec.java:152)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.RedirectExec.execute(RedirectExec.java:115)
        at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
        at org.apache.hc.client5.http.impl.classic.InternalHttpClient.doExecute(InternalHttpClient.java:170)
        ... 19 more
java apache-httpcomponents apache-httpclient-5.x
1个回答
0
投票

DefaultHttpRequestRetryStrategy已在定义中排除异常列表,请参阅它们此处

Arrays.asList(InterruptedIOException.class,UnknownHostException.class,ConnectException.class,ConnectionClosedException.class,NoRouteToHostException.class,SSLException.class)

最简单的方法是实施自定义策略并开始添加例外情况。

public class Main {
    public static void main(String[] args) throws IOException {
        final ClassicHttpRequest httpGet = ClassicRequestBuilder.get("https://httpbin.org/status/500")
                .build();

        CloseableHttpClient httpClient = HttpClientBuilder
                .create()
                .setRetryStrategy(new CustomRetryStrategy(3, TimeValue.ofSeconds(3)))
                .build();

        httpClient.execute(httpGet, response -> {
            System.out.println(response.getCode() + " " + response.getReasonPhrase());
            return null;
        });
    }

    @Slf4j
    static class CustomRetryStrategy implements HttpRequestRetryStrategy {
        private final int maxRetries;
        private final TimeValue retryInterval;

        public CustomRetryStrategy(final int maxRetries, final TimeValue retryInterval) {
            this.maxRetries = maxRetries;
            this.retryInterval = retryInterval;
        }

        @Override
        public boolean retryRequest(
                final HttpRequest request,
                final IOException exception,
                final int execCount,
                final HttpContext context) {
            Args.notNull(request, "request");
            Args.notNull(exception, "exception");

            if (execCount > this.maxRetries) {
                // Do not retry if over max retries
                return false;
            }
            return true;
        }

        @Override
        public boolean retryRequest(
                final HttpResponse response,
                final int execCount,
                final HttpContext context) {
            Args.notNull(response, "response");

            return execCount <= this.maxRetries;
        }

        @Override
        public TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context) {
            log.warn("Retrying HTTP request after " + retryInterval.toString());
            return retryInterval;
        }
    }
}

我的本地测试输出如下所示

23:18:03: Executing ':Main.main()'...

> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes

> Task :Main.main()
[main] WARN io.github.ozkanpakdil.Main$CustomRetryStrategy - Retrying HTTP request after 3 SECONDS
[main] WARN io.github.ozkanpakdil.Main$CustomRetryStrategy - Retrying HTTP request after 3 SECONDS
[main] WARN io.github.ozkanpakdil.Main$CustomRetryStrategy - Retrying HTTP request after 3 SECONDS
500 INTERNAL SERVER ERROR

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.5/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 11s
2 actionable tasks: 2 executed
23:18:15: Execution finished ':Main.main()'.
© www.soinside.com 2019 - 2024. All rights reserved.