我们使用JDK11 java.net.http
HTTP客户端从API获取数据。收到响应后,连接仍在我们的服务器中打开,状态为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进程,连接将消失。
HTTP客户端发送的标头:
connection: 'Upgrade, HTTP2-Settings','content-length': '0',
host: 'localhost:3000', 'http2-settings': 'AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA',
upgrade: 'h2c',
user-agent': 'Java-http-client/12'
服务器返回带标题的响应:Connection: close
我们试图在实现类jdk.internal.net.http.ConnectionPool
中微调池参数。
它没有解决问题。
System.setProperty("jdk.httpclient.keepalive.timeout", "5"); // seconds
System.setProperty("jdk.httpclient.connectionPoolSize", "1");
使用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中工作,这样我们就不必重写代码了。
我不建议为每个新请求创建一个新客户端。这违背了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来说并不是非常友好。
提交并确认为实施中的错误。