Java Micronaut重复调用端点后抛出javax.net.ssl.SSLHandshakeException异常问题排查
看起来你遇到的是典型的连接复用导致的SSL会话握手失败问题——第一次请求成功是因为建立了新连接并完成了完整握手,但第二次请求尝试复用之前的连接时,外部服务器拒绝了会话复用,从而抛出SSLHandshakeException。结合你提到的多个端点都出现该问题、Postman调用正常(Postman默认对连接复用的处理更灵活,可能每次请求都新建连接),下面是几个针对性的解决方案:
1. 禁用HTTP连接复用(快速验证)
Micronaut的HttpClient默认启用连接池和keep-alive机制来复用连接,不少外部API服务器对连接复用的支持有限,或者会话超时设置极短。你可以先尝试禁用keep-alive,强制每次请求建立新连接:
方式一:全局配置(application.yml)
在配置文件中添加以下设置,关闭连接复用和连接池的闲置保留:
micronaut: http: client: keep-alive: false connection-pool: max-connections-per-route: 1 max-idle-seconds: 0
方式二:单个请求级别配置
如果不想全局禁用,也可以在每次创建请求时手动添加Connection: close头,强制请求结束后关闭连接:
import io.micronaut.http.HttpHeaders; // 修改请求创建逻辑 HttpRequest<?> request = GET(newDeckUrl) .header(HttpHeaders.CONNECTION, "close"); Disposable result = this.httpClient .retrieve(request) .subscribe(data -> System.out.println(data));
2. 调整RxHttpClient的订阅资源管理
你当前用subscribe()异步处理响应,如果没有正确管理订阅资源,可能导致连接池中的连接被异常占用。可以先改用阻塞调用验证问题是否存在:
// 改用阻塞式调用测试 public void drawCardRx(String deckId, int amount){ final String newDeckUrl = "/deck/"+deckId+"/draw/?count="+amount; try { String data = this.httpClient.toBlocking().retrieve(GET(newDeckUrl)); System.out.println(data); } catch (Exception e) { e.printStackTrace(); } }
如果阻塞调用能正常重复执行,说明问题出在异步订阅的资源管理上,此时可以考虑用Flowable的autoDispose()或者手动管理Disposable的生命周期。
3. 启用SSL调试日志定位根因
如果上面的方法没解决问题,建议开启SSL调试日志,查看握手失败的具体细节。在JVM启动参数中添加:
-Djavax.net.debug=ssl:handshake
启动应用后再次调用端点,日志会输出完整的SSL握手流程,你能看到服务器发送handshake_failure的具体触发点(比如是会话复用请求被拒绝,还是证书链验证问题等),这能帮你更精准地定位问题。
4. 调整SSL会话缓存配置
虽然第一次请求成功说明证书信任没问题,但如果自定义了SSL上下文,可能存在会话缓存配置不当的情况。可以尝试让Micronaut禁用SSL会话缓存:
micronaut: ssl: client: session-cache-size: 0
建议先从禁用连接复用开始测试,这是解决此类问题最常见且快速的方法。如果问题解决了,就说明是外部服务器不支持连接复用导致的;如果没解决,再通过SSL日志进一步排查细节。
内容的提问来源于stack exchange,提问作者jpz

