Соединение закрывается при длительном сне между запросами HttpClient

Я впервые использую Apache HttpComponents и, в частности, HttpClient. У меня есть довольно простой вариант использования. Я опрашиваю сторонний сервер каждые N секунд (в данном случае их API ожидает POST).

CloseableHttpClient httpclient = HttpClients.custom().
    setDefaultCookieStore(cookieStore).build();

Затем я выполняю некоторые запросы POST в цикле... используя:

while (!someCondition) {
    HttpPost httpPost = ...
    httpclient.execute(httpPost)
    Thread.sleep(SOME_TIME)
}

Я заметил, что если я сплю дольше, например, 3 минуты, то я не получаю ответа от сервера, и соединение каждый раз обрывается:

DEBUG [org.apache.http.wire] http-outgoing-1 << "end of stream"
DEBUG [org.apache.http.impl.conn.DefaultManagedHttpClientConnection] http-outgoing-1: Close connection
DEBUG [org.apache.http.impl.conn.DefaultManagedHttpClientConnection] http-outgoing-1: Shutdown connection
DEBUG [org.apache.http.impl.execchain.MainClientExec] Connection discarded
DEBUG [org.apache.http.impl.conn.DefaultManagedHttpClientConnection] http-outgoing-1: Close connection

Я не верю, что это сервер. Вероятно, я неправильно использую HttpComponents или неправильно настроил его. Если я установлю его на более короткую продолжительность, например 1 минуту, он работает нормально (я заметил, что он умер после работы ~ 15 минут - так что это пятнадцать минутных интервалов).

Чтобы отправить запрос, я завернул его в несколько лямбда-выражений Java 8 и использовал попытку с ресурсами, но я не думаю, что это имеет значение:

private <R> R sendRequest(HttpUriRequest request, Function<String, R> func) {
    try {
        try (CloseableHttpResponse resp = this.httpclient.execute(request)) {
            HttpEntity entity = resp.getEntity();

            String responseString = EntityUtils.toString(entity);

            R result = null;
            if (func != null) {
                result = func.apply(responseString);
            }

            EntityUtils.consume(entity);
            return result;
        }
    } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException("Error sending request: " + request, e);
    }
}

Фактическое исключение:

Caused by: org.apache.http.NoHttpResponseException: myserver.com:80 failed to respond
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:143)
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:57)
    at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:261)
    at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:165)
    at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:167)
    at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:272)
    at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:124)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:271)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)

person nogridbag    schedule 18.04.2015    source источник
comment
Является ли последнее исключение, которое вы показали, действительно основной причиной исключения? Ничего из сетевого уровня Java (IOException?)   -  person Michael Heß    schedule 18.04.2015
comment
Я так считаю. В настоящее время он работает около 25 минут (при продолжительности сна ~ 30 секунд), поэтому я не хочу убивать его сейчас. Но я буду следить за этим.   -  person nogridbag    schedule 18.04.2015
comment
Есть ли брандмауэр между вашим клиентом и http-сервером?   -  person Michael Heß    schedule 18.04.2015
comment
Если вы видите в журнале «http-outgoing-1 ‹‹ end of stream», это в основном означает, что соединение было закрыто противоположной конечной точкой.   -  person ok2c    schedule 18.04.2015
comment
@oleg Я понимаю, что, похоже, это происходит. Я не понимаю, почему, если я создаю экземпляр HttpClient, отправляю один запрос, жду 3 минуты и отправляю другой запрос, он каждый раз терпит неудачу. Но если вместо этого я сплю ~ 30 секунд, он может нормально работать почти 2 часа. Должен ли я создавать новый экземпляр HttpClient каждый раз, когда я хочу вместо этого отправить запрос? Я предположил, что у меня может быть один экземпляр, и он управляет соединениями пула, обработкой повторных попыток и т. д. для меня (поскольку я использую эти конфигурации по умолчанию).   -  person nogridbag    schedule 18.04.2015
comment
Я изменил свой код, чтобы код начальной загрузки для отправки некоторых первоначальных запросов использовал один экземпляр HttpClient. Этот экземпляр закрывается, а затем он входит в метод длинного цикла. В методе цикла я создаю новый экземпляр HttpClient, отправляю несколько сообщений POST, изящно закрываю, жду 3 минуты и повторяю, и, похоже, он работает нормально. Возможно, я неправильно понимаю, что HttpClient аналогичен моему веб-браузеру, и не имеет значения, как долго я жду между запросами (и если какие-либо повторно используемые внутренние соединения умирают, я ожидал, что он создаст новые!).   -  person nogridbag    schedule 18.04.2015
comment
@nogridbag Какую версию HttpClient используете? В любом случае вместо того, чтобы отбрасывать экземпляр HttpClient, вам следует исключить все постоянные соединения из пула. Как только я узнаю версию, которую вы используете, я смогу дать вам более подробный ответ.   -  person ok2c    schedule 18.04.2015
comment
Спасибо - httpcomponents 4.4   -  person nogridbag    schedule 19.04.2015


Ответы (2)


Для HTTP-серверов очень часто и естественно закрывать постоянные соединения, которые простаивали в течение максимального периода бездействия, в целях экономии ресурсов. В вашем конкретном случае, если клиент генерирует запрос каждые 30 секунд, сервер поддерживает соединение, тогда как, если соединение остается бездействующим дольше, оно в конечном итоге закрывается на стороне сервера (и становится полузакрытым или «устаревшим» на стороне клиента) . В следующий раз, когда клиент арендует соединение из пула и попытается использовать его для выполнения запроса, выполнение завершится ошибкой с исключением ввода-вывода. Это совершенно нормально, и этого можно ожидать. HttpClient пытается смягчить проблему, выполняя так называемую проверку устаревшего соединения, чтобы выяснить, было ли соединение закрыто противоположной конечной точкой или стало недействительным в противном случае. «Устаревшие» чеки относительно дороги, и их следует использовать с осторожностью. HttpClient начиная с версии 4.4 стремится повысить производительность «из коробки», выполняя выборочную проверку. В версии 4.4 был дефект, который вообще отключал проверку соединения. Пожалуйста, обновитесь до версии 4.4.1, и проблема должна исчезнуть.

Сказав все это, я по-прежнему считаю, что лучшим способом действий в вашем случае является удаление всех постоянных соединений из пула соединений до длительных периодов бездействия с помощью HttpClientConnectionManager#closeIdleConnections

person ok2c    schedule 19.04.2015
comment
Спасибо! В моем конкретном случае я не знаю, есть ли какой-то конкретный недостаток в закрытии HttpClient, позволении ему получить сборщик мусора и создании нового экземпляра. Любой прирост производительности за счет сохранения одного и того же экземпляра HttpClient незначителен, поскольку я сплю порядка минут между запросами. Есть ли какая-либо другая причина, кроме производительности, для хранения одного экземпляра HttpClient? Спасибо еще раз. - person nogridbag; 19.04.2015
comment
Сохранение состояния разговора: файлы cookie, кеш авторизации, токен пользователя и т. д. - person ok2c; 19.04.2015

У меня была аналогичная проблема (NoHttpResponseException: myserver:port не ответил) со всеми работающими запросами GET и сбоями всех запросов POST и PUT. Это произошло из-за того, что HTTP-клиент по умолчанию сжимал тело запроса, а сервер не ожидал его сжатия и обрабатывал запрос как пустой.

Это были журналы на стороне сервера:

[2017-03-01 11:32:51,074][WARN ][http.netty][host-elasticsearch] Caught exception while handling client http traffic, closing connection [details]
TransportException[Support for compressed content is disabled. You can enable it with http.compression=true]

[2017-03-01 11:32:51,074][WARN ][http.netty] [host-elasticsearch] Caught exception while handling client http traffic, closing connection [details]
java.lang.IllegalStateException: received HttpChunk without HttpMessage

Я использовал jerseyClient (Dropwizard) и решил проблему, просто установив в конфигурации yaml:

jersey-client:
  gzipEnabled: false

Надеюсь, это поможет кому-то.

person Gabe    schedule 01.03.2017