Java 11 连接重置(间歇性)

Luc*_*tto 5 java java-http-client java-11

我使用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);
}
Run Code Online (Sandbox Code Playgroud)

以下是我如何创建连接管理器、客户端构建器和请求本身:

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;
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

当它出错时,它不会到达记录的成功请求中的部分:

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",
Run Code Online (Sandbox Code Playgroud)

我看到了SocketTimeoutException(甚至在请求开始后仅 200 毫秒就发生了这种情况)并尝试在连接管理器和客户端构建器中以及在请求中增加套接字超时,但它没有工作。我还尝试删除 keepalive 并使其不重复使用连接,但它也不起作用。我还在 SO 中查看了其他问题,但没有解决类似问题。以下是我尝试的更改:

在连接管理器中:

SocketConfig socketConfig = SocketConfig.custom()
    .setSoKeepAlive(false)
    .setSoTimeout(600000)
    .setSoReuseAddress(false)
    .build();
cm.setDefaultSocketConfig(socketConfig);
Run Code Online (Sandbox Code Playgroud)

在客户端:

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;
});
Run Code Online (Sandbox Code Playgroud)

在请求中:

RequestConfig requestConfig = RequestConfig.custom()
    .setSocketTimeout(15000)
    .setConnectTimeout(15000)
    .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
httpUriRequest.setConfig(requestConfig);
Run Code Online (Sandbox Code Playgroud)

我一起尝试了上述方法,也单独尝试过,但都没有奏效。

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

现在,我将它们包含在一个while循环中,该循环最多执行 4 次(当它出错时),以使对特定端点的请求无缝地对用户工作,但这是一种非常黑客的方法,我想解决这个问题,如果可能的。

我希望有人能帮我解决这个问题。