在 java 11 的 SSL 通信期间发生 SocketException 时,如何使用 DefaultHttpRequestRetryHandler(HTTPClient) 重试?
How can I retry using DefaultHttpRequestRetryHandler(HTTPClient) when SocketException occurs during SSL communication on java 11?
我应该如何实现才能在 java11 上使用 httpclient 重试 SSL 通信?
像下面这样覆盖DefaultHttpRequestRetryHandler是不是更好一些?
有没有更好的办法?
public class HttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
IOException cause = exception;
if (exception instanceof SSLException) {
if (exception.getCause() != null && exception.getCause() instanceof IOException) {
cause = (IOException) exception.getCause();
}
}
return super.retryRequest(cause, executionCount, context);
}
}
在Java11中,没有重试SocketException,因为SocketException被SSLException包裹了。
我阅读了以下内容
- https://bugs.openjdk.java.net/browse/JDK-8214339
http://hg.openjdk.java.net/jdk-updates/jdk11u/rev/18b4acfaaa97
if ((cause != null) && (cause instanceof IOException)) {
ssle = new SSLException(reason);
}
https://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html
当发生 SSLException 时,DefaultHttpRequestRetryHandler 不会重试。
使用java11时,随机出现以下错误的概率小于0.01%。
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:321)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:264)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:259)
at java.base/sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1314)
at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:839)
at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)
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)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:87)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735)
... 81 common frames omitted
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:352)
... 106 common frames omitted
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:1104)
at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:823)
... 102 common frames omitted
我创建了 HttpRequestRetryHandler class。
public class HttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
IOException cause = exception;
if (exception instanceof SSLException) {
if (exception.getCause() != null && exception.getCause() instanceof IOException) {
cause = (IOException) exception.getCause();
}
}
return super.retryRequest(cause, executionCount, context);
}
}
并将其设置为 HttpClientBuilder。
CloseableHttpClient httpclient = HttpClients.custom()
.setRetryHandler(new HttpRequestRetryHandler())
.build();
结论
- SSL 通信质量与使用 java8 时相同。
- 服务器或网络底层问题依然存在,但java11的影响已经解决
我应该如何实现才能在 java11 上使用 httpclient 重试 SSL 通信?
像下面这样覆盖DefaultHttpRequestRetryHandler是不是更好一些?
有没有更好的办法?
public class HttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
IOException cause = exception;
if (exception instanceof SSLException) {
if (exception.getCause() != null && exception.getCause() instanceof IOException) {
cause = (IOException) exception.getCause();
}
}
return super.retryRequest(cause, executionCount, context);
}
}
在Java11中,没有重试SocketException,因为SocketException被SSLException包裹了。
我阅读了以下内容
- https://bugs.openjdk.java.net/browse/JDK-8214339
http://hg.openjdk.java.net/jdk-updates/jdk11u/rev/18b4acfaaa97
if ((cause != null) && (cause instanceof IOException)) { ssle = new SSLException(reason); }
https://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html
当发生 SSLException 时,DefaultHttpRequestRetryHandler 不会重试。
使用java11时,随机出现以下错误的概率小于0.01%。
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:321)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:264)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:259)
at java.base/sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1314)
at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:839)
at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)
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)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:87)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735)
... 81 common frames omitted
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:352)
... 106 common frames omitted
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:1104)
at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:823)
... 102 common frames omitted
我创建了 HttpRequestRetryHandler class。
public class HttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
IOException cause = exception;
if (exception instanceof SSLException) {
if (exception.getCause() != null && exception.getCause() instanceof IOException) {
cause = (IOException) exception.getCause();
}
}
return super.retryRequest(cause, executionCount, context);
}
}
并将其设置为 HttpClientBuilder。
CloseableHttpClient httpclient = HttpClients.custom()
.setRetryHandler(new HttpRequestRetryHandler())
.build();
结论
- SSL 通信质量与使用 java8 时相同。
- 服务器或网络底层问题依然存在,但java11的影响已经解决