java - 不能在 java.net.http.HttpClient 上发出多个请求,否则将收到 : javax.net.ssl.SSLHandshakeException

标签 java exception httprequest sslhandshakeexception java-http-client

我在测试 Java 11 中的新 HttpClient 时发现了以下行为:

我正在向公共(public) REST API 发出两个异步请求以进行测试,并在一个客户端和两个单独的请求中进行了尝试。这个过程没有抛出任何异常。

String singleCommentUrl = "https://jsonplaceholder.typicode.com/comments/1";
String commentsUrl = "https://jsonplaceholder.typicode.com/comments";

Consumer<String> handleOneComment = s -> {
    Gson gson = new Gson();
    Comment comment = gson.fromJson(s, Comment.class);
    System.out.println(comment);
};
Consumer<String> handleListOfComments = s -> {
    Gson gson = new Gson();
    Comment[] comments = gson.fromJson(s, Comment[].class);
    List<Comment> commentList = Arrays.asList(comments);
    commentList.forEach(System.out::println);
};

HttpClient client = HttpClient.newBuilder().build();

client.sendAsync(HttpRequest.newBuilder(URI.create(singleCommentUrl)).build(), HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .thenAccept(handleOneComment)
        .join();

client.sendAsync(HttpRequest.newBuilder(URI.create(commentsUrl)).build(), HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .thenAccept(handleListOfComments)
        .join();

然后我尝试将 HttpClient 重构为一个方法,当它尝试发出第二个请求时出现以下异常:

public void run() {
    String singleCommentUrl = "https://jsonplaceholder.typicode.com/comments/1";
    String commentsUrl = "https://jsonplaceholder.typicode.com/comments";

    Consumer<String> handleOneComment = s -> {
        Gson gson = new Gson();
        Comment comment = gson.fromJson(s, Comment.class);
        System.out.println(comment);
    };
    Consumer<String> handleListOfComments = s -> {
        Gson gson = new Gson();
        Comment[] comments = gson.fromJson(s, Comment[].class);
        List<Comment> commentList = Arrays.asList(comments);
        commentList.forEach(System.out::println);
    };

    sendRequest(handleOneComment, singleCommentUrl);
    sendRequest(handleListOfComments, commentsUrl);
}

private void sendRequest(Consumer<String> onSucces, String url) {
    HttpClient client = HttpClient.newBuilder().build();
    HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();

    client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(onSucces)
            .join();
}

这会在成功执行第一个请求并在第二个请求失败后产生以下异常:

Exception in thread "main" java.util.concurrent.CompletionException: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at java.base/java.util.concurrent.CompletableFuture.encodeRelay(CompletableFuture.java:367)
    at java.base/java.util.concurrent.CompletableFuture.completeRelay(CompletableFuture.java:376)
    at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1074)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
    at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2088)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate.handleError(SSLFlowDelegate.java:904)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader.processData(SSLFlowDelegate.java:450)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader$ReaderDownstreamPusher.run(SSLFlowDelegate.java:263)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:128)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:308)
    at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:279)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:181)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
    at java.base/sun.security.ssl.SSLEngineImpl.decode(SSLEngineImpl.java:672)
    at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:627)
    at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:443)
    at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:422)
    at java.base/javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:634)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader.unwrapBuffer(SSLFlowDelegate.java:480)
    at java.net.http/jdk.internal.net.http.common.SSLFlowDelegate$Reader.processData(SSLFlowDelegate.java:389)
    ... 7 more

我也尝试通过方法中的参数传递单独的客户端和请求,但它产生了相同的结果。这是怎么回事?

最佳答案

显然,SSLContext对象不是线程安全的。 (假设任何其合约未明确保证线程安全的可变对象都不是线程安全的,这通常是正确的。)

HttpClients use the default SSLContext如果没有明确给出上下文。因此,您的两个请求似乎试图同时共享该默认上下文。

解决方案是为每个 HttpClient 指定一个全新的 SSLContext:

private void sendRequest(Consumer<String> onSucces, String url) {
    SSLContext context;
    try {
        context = SSLContext.getInstance("TLSv1.3");
        context.init(null, null, null);
    } catch (GeneralSecurityException e) {
        throw new RuntimeException(e);
    }

    HttpClient client = HttpClient.newBuilder().sslContext(context).build();
    HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build();

    client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(onSucces)
            .join();
}

关于java - 不能在 java.net.http.HttpClient 上发出多个请求,否则将收到 : javax.net.ssl.SSLHandshakeException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53765739/

相关文章:

java - 如何使光标可以输入 jtextfield 但给它一个文本的唯一方法是单击一个按钮?

java - JSP 中的多个参数包含

python - 我应该抛出哪个 Python 异常?

java - 如何将文本文件发送到显示错误 "request was rejected because no multipart boundary as found"的 http 请求

java - 如何请求 CONNECT 与 Apache HttpComponents 客户端

java - 为什么 lombok 突然停止了我的项目?

java - HTTP 请求未返回所需的 json

python - for 循环中的错误/异常处理 - python

Scala Future 错误处理与任一

http - 我的 GET 请求有什么问题?