Утечка соединений с состоянием CLOSE_WAIT с HttpClient - PullRequest
4 голосов
/ 21 марта 2019

Мы используем HTTP-клиент JDK11 java.net.http для получения данных из API.После получения ответа соединения остаются открытыми на нашем сервере с состоянием TCP CLOSE_WAIT, что означает, что клиент должен закрыть соединение.

С RFC 793 терминология:

CLOSE-WAIT - представляет ожидание запроса на завершение соединения от локального пользователя.

Это наш клиентский код, который работает на WildFly 16, работающем на Java 12, как API REST без сохранения состояния.Мы не понимаем, почему это происходит.

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

Update (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 ]

2 голосов
/ 22 марта 2019

Я бы не рекомендовал создавать нового клиента для каждого нового запроса. Это побеждает цель 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.

0 голосов
/ 25 марта 2019

Отправлено и подтверждено как ошибка в реализации.

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

...