Мерцающий HttpClient иногда выдает IOException
я используюjava.net.http.HttpClient.newHttpClient()
под Яву 19 (Темурин) и выполнитьsendAsync(...)
запросы из разных потоков на одном и том же экземпляре. Я предполагаю, что это нормально, как говорится в javadoc:
После создания HttpClient неизменяем...
Однако некоторые запросы не выполняются:
java.io.IOException: HTTP/1.1 header parser received no bytes
Странно то, что это зависит от скорости моих запросов:
- Запросы каждые 5 секунд: 30% сбоев
- Запросы каждые 3 секунды: 0% сбоев
Я написал для него тест:
private final HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://..."))
.setHeader("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofByteArray("[]".getBytes()))
.build();
@ParameterizedTest
@ValueSource(ints = {3, 5})
void httpClientTest(int intervalSeconds) throws Exception {
HttpClient httpClient = HttpClient.newHttpClient();
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()).get();
Thread.sleep(Duration.ofSeconds(intervalSeconds));
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()).get();
Thread.sleep(Duration.ofSeconds(intervalSeconds));
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()).get();
Thread.sleep(Duration.ofSeconds(intervalSeconds));
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()).get();
Thread.sleep(Duration.ofSeconds(intervalSeconds));
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofByteArray()).get();
}
Я уже пробовал следующее:
- Делая то же самое с
curl
в командной строке. Никакие запросы не терпят неудачу, какой бы интервал я ни пытался. Так что скорее всего проблема не в сервере. - Запуск тестов несколько раз параллельно. Тем не менее 5-секундные интервалы терпят неудачу (затем несколько раз параллельно). Так что скорее всего проблема не в сервере.
- Создание
HttpClient.newHttpClient()
для каждого запроса. Никакие запросы не терпят неудачу независимо от интервала. Так что, вероятно, проблема не в сервере, а во внутреннем состоянии (хотя оно и претендует на неизменность?).
У вас есть идея, что я мог бы сделать, не создавая новыйHttpClient
на каждый запрос?
3 ответа
Вот ответ для протокола:java.net.HttpClient
имеет длительное время проверки активности HTTP/1.1 по умолчанию, которое больше, чем настроено для обычных серверов. Это часто приводит к тому, что сервер закрывает простаивающие соединения HTTP/1.1 раньше, чем это сделает клиент. Если сервер закрывает соединение примерно в то же время, когда клиент пытается его повторно использовать, некоторыеIOException
может подняться.
Если такие исключения наблюдаются слишком часто, приложениям следует рассмотреть возможность адаптации времени поддержания активности по умолчанию в клиенте к некоторому значению, более короткому, чем то, которое используют серверы, к которым он подключается. Значение по умолчанию для HttpClient HTTP/1.1 keepAlive time можно указать в командной строке с помощью:-Djdk.httpclient.keepalive.timeout=duration-in-seconds
Так, например, если сервер настроен на время проверки активности 5 с, вы можете рассмотреть возможность предоставления-Djdk.httpclient.keepalive.timeout=3
или-Djdk.httpclient.keepalive.timeout=4
в командной строке Java клиента.
Для дополнительных записей, если кто-нибудь посмотрит информацию об этой ошибке:
- Установка http-версии
1.1
в сборщике может не решить вашу проблему: в HttpClient Javadoc сказано, что это предпочтительная версия и что она может быть не той, которая используется:
Например, если HTTP/2 запрашивается через прокси, и если реализация не поддерживает этот режим, то можно использовать HTTP/1.1.
- Если вы хотите отладить то, что происходит внутри http-клиента, вы можете использовать следующее системное свойство:
Djdk.httpclient.HttpClient.log=all
. Благодаря этому вы сможете увидеть, когда соединение закрывается удаленным сервером.
Это не доказано научно, но как человек, который «пытается увидеть» проблемные связи, вы можете попытаться убедить протокол явно отдавать предпочтение той или иной версии, а не переговорам или каким-либо еще скрытым вещам, вызывающим это горе. Например, вот пример, который помог в нашем случае:
// The server receiving the HTTP request must not support HTTP 2, so we have to explicitly set the version in the request to 1.1
val httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.build()