4、NoHttpResponseException和NonRepeatableRequestException: Cannot retry request with a non-repeatable解

1、项目场景:

项目中使用HttpClient post方法调用外部服务。


2、问题描述

2.1、使用HttpClient post方法调用外部服务时,偶发性出现如下异常

com.sun.jersey.api.client.ClientHandlerException: 
org.apache.http.NoHttpResponseException: XXX.XXX.com:80 failed to respond
	at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:187)
	at com.sun.jersey.api.client.Client.handle(Client.java:652)
	at com.sun.jersey.api.client.filter.LoggingFilter.handle(LoggingFilter.java:217)
	at com.cheche365.cheche.signature.client.ClientSignatureFilter.handle(ClientSignatureFilter.java:39)
	at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682)

2.2、设置重试机制,新增createHttpRequestRetryHandler方法,实现方案如下

static {
        CLIENT_CONFIG = new DefaultApacheHttpClient4Config();
        CLIENT_CONFIG.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);

        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
        } catch (Exception e) {
            e.printStackTrace();
        }
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER))
                .build();

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connectionManager.setMaxTotal(200);
        connectionManager.setDefaultMaxPerRoute(100);

        HTTP_CLIENT_BUILDER = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(
                        RequestConfig.custom()
                                .setConnectionRequestTimeout(15 * 1000)
                                .setConnectTimeout(15 * 1000)
                                .setSocketTimeout(60 * 1000)
                                .build())
                .setRetryHandler(createHttpRequestRetryHandler())
                .evictIdleConnections(15, TimeUnit.SECONDS)
                .evictExpiredConnections()
                .disableRedirectHandling();

    }

private static HttpRequestRetryHandler createHttpRequestRetryHandler() {
        return (exception, executionCount, context) -> executionCount <= 3 && exception instanceof NoHttpResponseException;
}

2.3、出现新的问题com.sun.jersey.api.client.ClientHandlerException: org.apache.http.client.ClientProtocolException 造成原因org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity

异常信息 : com.sun.jersey.api.client.ClientHandlerException: org.apache.http.client.ClientProtocolException
	at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:187)
	at com.sun.jersey.api.client.filter.LoggingFilter.handle(LoggingFilter.java:217)
	at com.sun.jersey.api.client.Client.handle(Client.java:652)
	at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682)
	at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
	at com.sun.jersey.api.client.WebResource$Builder.method(WebResource.java:634)
Caused by: org.apache.http.client.ClientProtocolException
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:187)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:72)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
	at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:171)
	... 77 more
Caused by: org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:108)
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
	... 80 more
Caused by: org.apache.http.NoHttpResponseException: The target server failed to respond
	at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:141)
	at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)
	at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
	at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
	at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:157)
	at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)
	at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:272)
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
	... 81 more

3、原因分析:

问题2.1出现是因为请求超时,没有收到响应,使用http请求重试机制,在出现NoHttpResponseException异常时,直接重试,进而出现2.3异常ClientProtocolException 造成原因是Cannot retry request with a non-repeatable request entity,因此修改使用ApacheHttpClient4Config.PROPERTY_ENABLE_BUFFERING协议即可解决。


4、解决方案:

创建Client的时候增加配置使用ApacheHttpClient4Config.PROPERTY_ENABLE_BUFFERING协议,详细代码如下:

public static Function<String, Client> createClientFunction(Consumer<Client> customerFilter, Boolean needDefaultFilter, Boolean useBufferingProperty) {
        return (host) -> {
            CloseableHttpClient closeableHttpClient = HTTP_CLIENT_BUILDER.build();
            if (useBufferingProperty){
                CLIENT_CONFIG.getProperties().put(ApacheHttpClient4Config.PROPERTY_ENABLE_BUFFERING, Boolean.TRUE);
            }
            Client client = new Client(new ApacheHttpClient4Handler(closeableHttpClient, new BasicCookieStore(), true), CLIENT_CONFIG);
            if (needDefaultFilter) {
                client.addFilter(new LoggingFilter(Logger.getLogger(ExternalAPI.class.getName())));
            }
            if (!Objects.isNull(customerFilter)) {
                customerFilter.accept(client);
            }
            return client;
        };
    }

5、小提示:

使用ApacheHttpClient4Config.PROPERTY_ENABLE_BUFFERING协议可能会出现内存溢出问题,源码里面注释如下:

/**
     * If {@code true} then chunk encoding will be disabled and entity will be buffered
     * in the client in order to calculate the size of the entity. When property
     * is {@code false} then chunk encoding will be enabled. In that case the
     * property {@link ClientConfig#PROPERTY_CHUNKED_ENCODING_SIZE} can be
     * used to control the size of the chunk.
     * <p>
     * Note that the behaviour of the http client differs from the default client
     * configuration in the way that the chunk encoding is enabled by default and must
     * be disabled if needed. When entity buffering is enabled then the whole entity is
     * buffered and might cause out of memory errors if the entity is too large.
     * <p/>
     * <p>
     * Property must be of a {@link Boolean} type. Default value is {@code false}.
     * </p>
     *
     */
    public static final String PROPERTY_ENABLE_BUFFERING =
            "com.sun.jersey.impl.client.httpclient.enableBuffering";
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白de成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值