9

我们正在使用 JDK11 java.net.httpHTTP 客户端从 API 获取数据。在我们收到响应后,连接在我们的服务器中仍然以 TCP 状态打开CLOSE_WAIT,这意味着客户端必须关闭连接。

来自RFC 793术语:

CLOSE-WAIT - 表示等待来自本地用户的连接终止请求。

这是我们的客户端代码,它在 WildFly 16 上运行,在 Java 12 上作为无状态 REST API 运行。我们不明白为什么会这样。

import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;

public class SocketSandbox {

    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        try (var listener = new ServerSocket(59090)) {
            System.out.println("Server is running...");
            while (true) {
                try (var socket = listener.accept()) {
                    HttpRequest request = HttpRequest
                            .newBuilder(URI.create("<remote_URL>"))
                            .header("content-type", "application/json").build();
                    HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
                    var out = new PrintWriter(socket.getOutputStream(), true);
                    out.println(String.format("Response HTTP status: %s", response.statusCode()));
                }
            }
        }
    }

}

我们得到“状态码”,表示 http 响应已处理。

当使用相同的代码调用其他端点时,连接很好。这似乎是我们正在调用的远程 API 的一个特殊问题,但我们仍然不明白为什么 Java HTTP 客户端保持连接打开。

我们尝试了 Windows 和 Linux 机器,甚至尝试了 WildfFly 之外的独立机器,但结果相同。在每次请求之后,即使是从我们的无状态客户端执行并接收响应,每个请求都将保持不变CLOSE_WAIT并且永远不会关闭。

如果我们关闭 Java 进程,连接将消失。

在此处输入图像描述

HTTP 客户端发送的标头:

connection: 'Upgrade, HTTP2-Settings','content-length': '0',
host: 'localhost:3000', 'http2-settings': 'AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA',
upgrade: 'h2c',
user-agent': 'Java-http-client/12'

服务器返回带有标题的响应:Connection: close

更新 (1)

我们尝试微调实现类中的池参数jdk.internal.net.http.ConnectionPool

它没有解决问题。

System.setProperty("jdk.httpclient.keepalive.timeout", "5"); // seconds
System.setProperty("jdk.httpclient.connectionPoolSize", "1");

更新 (2)

使用Apache HTTP时,连接会处于 CLOSE_WAIT 状态大约 90 秒,但在那之后它可以连接。

调用方法HttpGet.releaseConnection()强制连接立即关闭。

HttpClient client = HttpClients.createDefault();
HttpGet get = new HttpGet("https://<placeholderdomain>/api/incidents/list");
get.addHeader("content-type", "application/json");
HttpResponse response = client.execute(get);

// This right here did the trick
get.releaseConnection();

return response.getStatusLine().getStatusCode();

并且使用OkHttp客户端,它可以按预期开箱即用,没有连接卡住。

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
        .url("https://<placeholderdomain>/grb/sb/incidentes/listar")
        .header("content-type", "application/json").build();
Response response = client.newCall(request).execute();
return response.body().string();

我们仍在努力寻找如何让它在 java-http-client 中工作,这样我们就不必重写代码。

4

2 回答 2

5

提交并确认为实现中的错误。

https://bugs.openjdk.java.net/browse/JDK-8221395

更新

检查 JIRA 问题,它已在 JDK 13 中修复,并向后移植到 11.0.6。(不确定12)

于 2019-03-25T14:39:29.887 回答
2

我不建议为每个新请求创建一个新客户端。这违背了 HTTP/2 的目的,它允许在单个连接上多路复用请求。

第二件事是这两个属性:

System.setProperty("jdk.httpclient.keepalive.timeout", "5"); // seconds
System.setProperty("jdk.httpclient.connectionPoolSize", "1");

仅适用于 HTTP/1.1 连接,不适用于 HTTP/2。还要注意这些属性只在类加载时读取一次。因此,在加载类之后设置它们java.net.http将无效。

最后,在释放所有保持活动连接之前可能需要一段时间HttpClient才能关闭 - 这样做的内部机制基本上依赖于 GC - 这对短寿命的 HttpClients 不是很友好。

于 2019-03-22T12:00:55.717 回答