5

我正在使用Java11并向httpclient 4.5.11API 发出请求,到目前为止一切正常。但是一个多星期前,付款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、headers 和其他数据,但请求本身是由 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 毫秒发生)并尝试在连接管理器以及客户端构建器以及请求中增加套接字超时,但它没有工作。我还尝试删除keepalive并使其不被重用,但它也没有工作。我还在这里查看了类似问题的其他问题,但没有一个有效。以下是我尝试的更改:

在连接管理器中:

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 的方法,我想解决这个问题,如果可能的。

我希望有人可以帮助我。

4

0 回答 0