Java 11连接重置(间歇性)

时间:2020-06-23 15:56:06

标签: java java-11 java-http-client

我正在使用Java11httpclient 4.5.11向API发出请求,到目前为止一切正常。但是一周多以前,向https://api.moip.com.br的付款开始出现间歇性错误(Connection reset)。

我知道他们需要TLSv1.2,并且Java11支持它,而且错误是间歇性的(某些东西起作用,有时却不起作用)。我联系了他们,他们说他们进行了代码库的迁移,但是他们处理请求的配置是相同的,对此我表示怀疑,但是无论如何,考虑到他们处理多个组织的付款,我认为存在一些问题。我可以避免这些错误的方法。附带说明,我可以毫无问题地向PayPal,Stripe,Google Recaptcha和其他服务发出请求。

我如何创建连接:

try (CloseableHttpClient httpClient = createHttpClientBuilder().build()) {
    HttpUriRequest httpMethod = createHttpUriRequest(request);
    HttpResponse httpResponse = httpClient.execute(httpMethod);
    SimpleHttpClientResponse response = createResponse(httpResponse);
    return response;
} catch (Exception e) {
    throw new HTTPException(request, e);
}

以下是我创建连接管理器,客户端构建器和请求本身的方法:

private static HttpClientBuilder createHttpClientBuilder() throws KeyManagementException, NoSuchAlgorithmException {
    CookieStore httpCookieStore = new BasicCookieStore();
    RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();

    HttpClientBuilder builder = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .setConnectionManagerShared(true)
            .setDefaultCookieStore(httpCookieStore)
            .setDefaultRequestConfig(defaultRequestConfig);

    return builder;
}

private static PoolingHttpClientConnectionManager createConnectionManager() {
    try {
        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
                SSLContext.getDefault(),
                new String[] {"TLSv1.2"},
                null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", socketFactory)
                .build();

        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
        cm.setMaxTotal(200);
        cm.setDefaultMaxPerRoute(20);

        return cm;
    } catch (NoSuchAlgorithmException | RuntimeException e) {
        LogUtils.error(e);
        return null;
    }
}

private static HttpUriRequest createHttpUriRequest(SimpleHttpClientRequest request) throws ParseException, IOException, URISyntaxException {
    HttpUriRequest httpUriRequest;

    if (request.getMethod().equals(HTTPMethod.POST)) {
        HttpPost httpPost = new HttpPost(request.getUrl());
        HttpEntity entity = createEntity(request);
        httpPost.setEntity(entity);
        httpUriRequest = httpPost;
    } else if (request.getMethod().equals(HTTPMethod.HEAD)) {
        httpUriRequest = new HttpHead(getUrlWithParameters(request));
    } else if (request.getMethod().equals(HTTPMethod.DELETE)) {
        httpUriRequest = new HttpDelete(getUrlWithParameters(request));
    } else {
        httpUriRequest = new HttpGet(getUrlWithParameters(request));
    }

    Map<String, String> headers = request.getHeaders();

    if (headers != null) {
        for (String key : headers.keySet()) {
            String value = headers.get(key);
            httpUriRequest.setHeader(key, value);
        }
    }

    return httpUriRequest;
}

SimpleHttpClientRequest只是我的一类,它定义发出请求时要使用的uri,标头和其他数据,但是请求本身由apache HttpClient完成。

就像我说的那样,如果我多次尝试发出相同的请求,则错误是间歇性的。

我定义了-Djavax.net.debug=all以了解可能的情况,日志如下所示:

Obs:我删除了ERROR [stderr] (default task-2) 0000: D2 A3 F4 C9 87 BF 23 89 65 F7 50 B6 90 8C 9D 8B ......#.e.P.....之类的行,因为在这种情况下我不认为它们会添加任何有用的信息

当我收到错误消息时:

10:03:16,752 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23             
10:03:16.750 GMT-03:00|SSLSocketInputRecord.java:249|READ: TLSv1.1 application_data, length = 2160
10:03:16,756 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:16.755 GMT-03:00|SSLCipher.java:1329|Padded plaintext after DECRYPTION (
10:03:16,775 ERROR [stderr] (default task-2) )
10:03:16,793 ERROR [stderr] (default task-2) javax.net.ssl|WARNING|DC|default task-2|2020-06-23 10:03:16.792 GMT-03:00|SSLSocketImpl.java:1280|handling exception (
10:03:16,793 ERROR [stderr] (default task-2) "throwable" : {
10:03:16,793 ERROR [stderr] (default task-2)   java.net.SocketTimeoutException: Read timed out
10:03:16,793 ERROR [stderr] (default task-2)    at java.base/java.net.SocketInputStream.socketRead0(Native Method)
10:03:16,794 ERROR [stderr] (default task-2)    at java.base/java.net.SocketInputStream.socketRead(SocketInputStream.java:115)
10:03:16,794 ERROR [stderr] (default task-2)    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:168)
10:03:16,802 ERROR [stderr] (default task-2)    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
...
10:03:16,812 ERROR [stderr] (default task-2)    at java.base/java.lang.Thread.run(Thread.java:834)}
10:03:16,812 ERROR [stderr] (default task-2)
10:03:16,813 ERROR [stderr] (default task-2) )
10:03:16,820 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:16.813 GMT-03:00|SSLSocketOutputRecord.java:309|WRITE: TLS12 application_data, length = 329
10:03:16,821 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:16.821 GMT-03:00|SSLCipher.java:1743|Plaintext before ENCRYPTION (
10:03:16,827 ERROR [stderr] (default task-2) )
10:03:16,828 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:16.827 GMT-03:00|SSLSocketOutputRecord.java:323|Raw write (
10:03:16,829 ERROR [stderr] (default task-2) )
10:03:16,990 ERROR [stderr] (default task-2) javax.net.ssl|WARNING|DC|default task-2|2020-06-23 10:03:16.989 GMT-03:00|SSLSocketImpl.java:1280|handling exception (
10:03:16,990 ERROR [stderr] (default task-2) "throwable" : {
10:03:16,991 ERROR [stderr] (default task-2)   java.net.SocketException: Connection reset
10:03:16,991 ERROR [stderr] (default task-2)    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:186)
10:03:16,991 ERROR [stderr] (default task-2)    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
...
10:03:17,022 ERROR [stderr] (default task-2)    at java.base/java.lang.Thread.run(Thread.java:834)}
10:03:17,022 ERROR [stderr] (default task-2)
10:03:17,022 ERROR [stderr] (default task-2) )
10:03:17,025 ERROR [stderr] (default task-2) javax.net.ssl|ERROR|DC|default task-2|2020-06-23 10:03:17.024 GMT-03:00|TransportContext.java:318|Fatal (UNEXPECTED_MESSAGE): Connection reset (
10:03:17,025 ERROR [stderr] (default task-2) "throwable" : {
10:03:17,025 ERROR [stderr] (default task-2)   java.net.SocketException: Connection reset
10:03:17,026 ERROR [stderr] (default task-2)    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:186)
10:03:17,026 ERROR [stderr] (default task-2)    at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
10:03:17,026 ERROR [stderr] (default task-2)    at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:448)
...
10:03:17,052 ERROR [stderr] (default task-2)    at java.base/java.lang.Thread.run(Thread.java:834)}
10:03:17,052 ERROR [stderr] (default task-2)
10:03:17,052 ERROR [stderr] (default task-2) )
10:03:17,053 ERROR [stderr] (default task-2) javax.net.ssl|ALL|DC|default task-2|2020-06-23 10:03:17.053 GMT-03:00|SSLSessionImpl.java:784|Invalidated session:  Session(1592917276199|TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
10:03:17,055 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:17.054 GMT-03:00|SSLSocketOutputRecord.java:71|WRITE: TLS12 alert(unexpected_message), length = 10
10:03:17,056 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:17.055 GMT-03:00|SSLCipher.java:1743|Plaintext before ENCRYPTION (
10:03:17,056 ERROR [stderr] (default task-2) )
10:03:17,059 ERROR [stderr] (default task-2) javax.net.ssl|WARNING|DC|default task-2|2020-06-23 10:03:17.059 GMT-03:00|TransportContext.java:360|Fatal: failed to send fatal alert UNEXPECTED_MESSAGE (
10:03:17,060 ERROR [stderr] (default task-2) "throwable" : {
10:03:17,060 ERROR [stderr] (default task-2)   java.net.SocketException: Broken pipe (Write failed)
10:03:17,060 ERROR [stderr] (default task-2)    at java.base/java.net.SocketOutputStream.socketWrite0(Native Method)
10:03:17,061 ERROR [stderr] (default task-2)    at java.base/java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:110)
...
10:03:17,076 ERROR [stderr] (default task-2)    at java.base/java.lang.Thread.run(Thread.java:834)}
10:03:17,076 ERROR [stderr] (default task-2)
10:03:17,076 ERROR [stderr] (default task-2) )
10:03:17,077 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:17.077 GMT-03:00|SSLSocketImpl.java:1353|close the underlying socket
10:03:17,077 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:17.077 GMT-03:00|SSLSocketImpl.java:1372|close the SSL connection (initiative)
10:03:17,078 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:17.078 GMT-03:00|SSLSocketImpl.java:663|close outbound of SSLSocket
10:03:17,078 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:03:17.078 GMT-03:00|SSLSocketImpl.java:629|close inbound of SSLSocket
10:03:17,079 ERROR [stderr] (default task-2) javax.net.ssl|WARNING|DC|default task-2|2020-06-23 10:03:17.079 GMT-03:00|TransportContext.java:284|Closed transport, general or untracked problem
10:03:17,105 WARN  [my.company.util.LogUtils] (default task-2)
        at my.deployment//my.company.util.ExceptionUtils.wrap(ExceptionUtils.java:26)
        at my.deployment//my.company.util.PagamentoUtil.consultarRespostaHttpPagamento(PagamentoUtil.java:618)
        at my.deployment//my.company.controller.admin.verification.VerifyPaymentApiController.doGet(VerifyPaymentApiController.java:61)
        ... 58 more
Caused by: my.company.exceptions.HTTPException: javax.net.ssl.SSLException: Connection reset - 'GET': 'https://api.moip.com.br/v2/orders/ORD-KC1CSM2SVPMX'
        at my.deployment//my.company.util.HttpClientUtils.send(HttpClientUtils.java:63)
        at my.deployment//my.company.util.URIUtils.getResponseObjectFromRequest(URIUtils.java:315)
        at my.deployment//my.company.util.PagamentoUtil.accessMoipV2(PagamentoUtil.java:2060)
        at my.deployment//my.company.util.PagamentoUtil.lambda$consultarRespostaHttpPagamento$0(PagamentoUtil.java:597)
        at my.deployment//my.company.util.PagamentoUtil.getResponseResultFromApi(PagamentoUtil.java:806)
        at my.deployment//my.company.util.PagamentoUtil.consultarRespostaHttpPagamento(PagamentoUtil.java:597)
        ... 59 more
Caused by: javax.net.ssl.SSLException: Connection reset
        at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:127)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:326)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:269)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:264)
        at java.base/sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1306)
        at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:832)
        at my.deployment//org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
        at my.deployment//org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
        at my.deployment//org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
        at my.deployment//org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)
        at my.deployment//org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)
        at my.deployment//org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
        at my.deployment//org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
        at my.deployment//org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:157)
        at my.deployment//org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)
        at my.deployment//org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
        at my.deployment//org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:272)
        at my.deployment//org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
        at my.deployment//org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
        at my.deployment//org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
        at my.deployment//org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
        at my.deployment//org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
        at my.deployment//org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
        at my.deployment//my.company.util.HttpClientUtils.send(HttpClientUtils.java:59)
        ... 64 more
        Suppressed: java.net.SocketException: Broken pipe (Write failed)
                at java.base/java.net.SocketOutputStream.socketWrite0(Native Method)
                at java.base/java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:110)
                at java.base/java.net.SocketOutputStream.write(SocketOutputStream.java:150)
                at java.base/sun.security.ssl.SSLSocketOutputRecord.encodeAlert(SSLSocketOutputRecord.java:81)
                at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:357)
                ... 86 more
Caused by: java.net.SocketException: Connection reset
        at java.base/java.net.SocketInputStream.read(SocketInputStream.java:186)
        at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
        at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:448)
        at java.base/sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(SSLSocketInputRecord.java:68)
        at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1096)
        at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:816)
        ... 82 more

发生错误时,它不会到达成功记录的请求部分:

10:01:13,234 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:01:13.233 GMT-03:00|TrustStoreManager.java:161|Inaccessible trust store: /usr/lib/jvm/java-11-openjdk-11.0.7.10-4.el7_8.x86_64/lib/security/jssecacerts
10:01:13,234 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:01:13.234 GMT-03:00|TrustStoreManager.java:112|trustStore is: /usr/lib/jvm/java-11-openjdk-11.0.7.10-4.el7_8.x86_64/lib/security/cacerts
10:01:13,234 ERROR [stderr] (default task-2) trustStore type is: pkcs12
10:01:13,234 ERROR [stderr] (default task-2) trustStore provider is:
10:01:13,235 ERROR [stderr] (default task-2) the last modified time is: Thu Jun 11 13:31:42 GMT-03:00 2020
10:01:13,553 ERROR [stderr] (default task-2) javax.net.ssl|DEBUG|DC|default task-2|2020-06-23 10:01:13.551 GMT-03:00|X509TrustManagerImpl.java:79|adding as trusted certificates (
10:01:13,554 ERROR [stderr] (default task-2)   "certificate" : {
10:01:13,554 ERROR [stderr] (default task-2)     "version"            : "v3",

我看到了SocketTimeoutException(即使在请求开始后仅200毫秒就发生这种情况,我也很努力),试图在连接管理器,客户端构建器以及请求中增加套接字超时,但是它没有用。我还尝试删除并使其保持连接状态,以使连接不被重用,但是它也不起作用。我还在SO中查看了其他类似问题,但都无济于事。以下是我尝试的更改:

在连接管理器中:

SocketConfig socketConfig = SocketConfig.custom()
    .setSoKeepAlive(false)
    .setSoTimeout(600000)
    .setSoReuseAddress(false)
    .build();
cm.setDefaultSocketConfig(socketConfig);

在客户端中:

RequestConfig requestConfig = RequestConfig.custom()
    .setSocketTimeout(15000)
    .setConnectTimeout(15000)
    .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
builder = builder.setDefaultRequestConfig(requestConfig);
builder = builder.setConnectionReuseStrategy((response, context) -> {
    LogUtils.info("**** connectionReuse strategy returning false");
    return false;
});
builder = builder.setKeepAliveStrategy((response, context) -> {
    LogUtils.info("**** keepAlive strategy returning -1");
    return -1l;
});

在请求中:

RequestConfig requestConfig = RequestConfig.custom()
    .setSocketTimeout(15000)
    .setConnectTimeout(15000)
    .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
httpUriRequest.setConfig(requestConfig);

我一起尝试了上述方法,也没有分别尝试。

我意识到的一件事情是,在本地进行测试(可能是巧合,艰难)后,在启动Java应用程序后我从未收到异常,并且当一个请求有效时,以下请求可以工作1分钟左右,但即使我定义为不重用连接,该连接以相同的方式继续进行,出现间歇性错误,并且在一个请求成功的情况下工作一段时间(尽管该配置并未禁用连接的重用,但是我认为它已禁用,因为我看到了日志connectionReuse strategy returning false),所以我最终没有选择。

现在,我将它们包含在while循环中,该循环最多执行4次(出现错误时),以使对该特定端点的请求无缝地对用户起作用,但这是一种非常hack的方法,我希望解决此问题。

我希望有人可以帮助我。

0 个答案:

没有答案