与HttpClient的状态为CLOSE_WAIT的连接泄漏

时间:2019-03-20 22:37:40

标签: java java-11 connection-leaks java-http-client

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

使用RFC 793术语:

  

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

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

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进程,连接将消失。

enter image description here

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中运行的方法,以便我们不必重写代码。

2 个答案:

答案 0 :(得分:2)

我不建议为每个新请求创建一个新客户端。这违反了HTTP / 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来说不是很友好。 / p>

答案 1 :(得分:0)

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

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