Проміжна помилка Apache HttpClient: NoHttpResponseException


80

У мене є веб-служба, яка приймає метод POST із XML. Це працює нормально, то в деяких випадкових випадках, він не може зв'язатись із сервером, викидаючи IOException із повідомленням The target server failed to respond. Подальші дзвінки працюють нормально.

Це трапляється здебільшого, коли я роблю кілька дзвінків, а потім залишаю програму в режимі очікування приблизно 10-15 хв. перший виклик, який я роблю після цього, повертає цю помилку.

Я спробував кілька речей ...

Я встановлюю обробник повторної спроби, як

HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {

            public boolean retryRequest(IOException e, int retryCount, HttpContext httpCtx) {
                if (retryCount >= 3){
                    Logger.warn(CALLER, "Maximum tries reached, exception would be thrown to outer block");
                    return false;
                }
                if (e instanceof org.apache.http.NoHttpResponseException){
                    Logger.warn(CALLER, "No response from server on "+retryCount+" call");
                    return true;
                }
                return false;
            }
        };

        httpPost.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, retryHandler);

але ця повторна спроба так і не отримала виклику. (так, я використовую речення right instanceof). При налагодженні цей клас ніколи не викликається.

Я навіть намагався налаштувати, HttpProtocolParams.setUseExpectContinue(httpClient.getParams(), false);але користі немає. Хтось може підказати, що я можу зробити зараз?

ВАЖЛИВО Окрім з'ясування, чому я отримую виняток, одне з важливих занепокоєнь у мене полягає в тому, чому тут не працює ретранслятор?


Я не думаю, що це щось із клієнтським кодом. Можливо, сервер призначення занадто зайнятий обробкою відповідей?
kosa

1
Я спробував скрипку бомбардувати цільовий сервер, але він працював нормально. Я навіть намагався виконати ті самі кроки, щоб відтворити помилку за допомогою скрипки, але не пощастило!
Em Ae

Який веб-сервер запускає службу, і протягом 10-15 хвилин очікування служба отримує інші запити, або служба не працює?
Шон

Tomcat. Немає фіктивного банкомату, і він не отримує нічого іншого, крім моїх дзвінків.
Em Ae

Я також отримую цю помилку, але в обгортці ResourceAccessException.
Сагар Хараб

Відповіді:


117

Швидше за все постійні зв’язки, які підтримує менеджер зв’язків, стають застарілими. Тобто цільовий сервер відключає з'єднання на своєму кінці, не маючи можливості HttpClient реагувати на цю подію, поки з'єднання перебуває в режимі очікування, роблячи з'єднання напівзакритим або `` застарілим ''. Зазвичай це не проблема. HttpClient використовує кілька методів для перевірки дійсності підключення після оренди з пулу. Навіть якщо перевірка застарілого з'єднання вимкнена, а застаріле з'єднання використовується для передачі повідомлення запиту, виконання запиту зазвичай не вдається в операції запису з SocketException і автоматично повторюється. Однак за деяких обставин операція запису може закінчитися без винятку, і наступна операція читання повертає -1 (кінець потоку).

Найпростіший спосіб виправити ситуацію - виселити з’єднання, термін придатності яких закінчився, і вони простоювали довше, ніж, скажімо, 1 хвилини з пулу після періоду бездіяльності. Детальніше див. У цьому розділі підручника HttpClient .


Я використовую HTTTPClient 4.5.1, тут для двох серверів безперервної повторної спроби не вдалося відповісти, але втретє вдалося, то чому він не підключається з першої спроби, навіть якщо я тримав час 1 хвилину, як зазначено.
Kundan Atre,

@oleg, Яка різниця між неробочими і простроченими з'єднаннями?
Алесь

1
Деякі з’єднання можуть мати час закінчення, після якого їх слід вважати недійсними / повторно використаними. Неактивні з’єднання є цілком дійсними та багаторазовими, але на мить не використовуються.
ok2c

2
Це залежить від багатьох факторів. Це повинно бути безпечним для безпечних та ідемпотентних методів до тих пір, поки сервер відповідає специфікації HTTP щодо безпеки методів та ідемпотентності.
ok2c

1
В моєму випадку використання одномінутного таймауту, як тут було рекомендовано, було саме причиною проблеми: клієнт apache спілкувався з локальним сервером причалу. AFAIK за замовчуванням, причал закриває з'єднання через 30 секунд бездіяльності. Установка connectionManager.setValidateAfterInactivity(500), тобто півсекунди, вирішила мою проблему. Думаю, кілька секунд зробили б однаково добре, але мені все одно.
maaartinus

21

Прийнята відповідь правильна, але не має рішення. Щоб уникнути цієї помилки, ви можете додати setHttpRequestRetryHandler (або setRetryHandler для компонентів apache 4.4) для вашого HTTP-клієнта, як у цій відповіді .


3
Беручи до уваги, що вихідне запитання визначає POST, чи є обробник повторної спроби правильним підходом тут?
Брайан Егнью

2
Ви запитуєте, чи безпечно повторити спробу POST? Як принцип проектування, PUT є ідемпотентним. Повторна спроба POST може мати непередбачені наслідки. Знову ж таки, не всі PUT записуються ідемпотентно.
activedecay

Практично безпечно повторити запит POST за допомогою запропонованого мною методу - оскільки він обробляє та повторює лише NoHttpResponseException, який набагато більш можливий завдяки клієнтській, а не серверній стороні.
Jehy

9
@Jehy Це зовсім не абсолютно безпечно ... NoHttpResponseException означає, що клієнт не отримав відповіді, а не те, що сервер не отримав та / або обробив запит
Lyrkan

безпека може бути забезпечена реалізацією, якщо частини вмісту будуть повторно використані під час створення ключа / ідентичності, сервер може виплюнути 400, визнаючи, що запис уже створений.
Баклажани

7

HttpClient 4.4 страждав від помилки в цій області, що стосується перевірки можливо застарілих з'єднань перед поверненням до запитувача. Він не перевірив, чи з’єднання застаріло, і це в результаті негайно NoHttpResponseException.

Цю проблему вирішено в HttpClient 4.4.1. Див. Цей JIRA та примітки до випуску


14
Я використовую версію 4.5.3, але все ще отримую NoHttpResponseException . У моєму випадку я отримую цей виняток у кожному 4-му чи 5-му безперервному запиті, а не в 1-му запиті. Будь-яка ідея, що може бути причиною в моєму випадку?
Sahil Chhabra

1
@SahilChhabra Той самий сценарій і на моїй стороні. Чи змогли ви знайти рішення своєї проблеми?
Manish Bansal

4

В даний час більшість з'єднань HTTP вважаються постійними, якщо не зазначено інше . Однак для збереження ресурсів сервера підключення рідко залишається відкритим назавжди, час очікування підключення за замовчуванням для багатьох серверів досить короткий, наприклад, 5 секунд для Apache httpd 2.2 і вище.

org.apache.http.NoHttpResponseExceptionПомилка відбувається , швидше за все , від одного постійного з'єднання , який був закритий на сервері.

Можна встановити максимальний час, щоб залишати невикористані підключення відкритими в пулі клієнтів Apache Http, в мілісекундах.

З Spring Boot один із способів досягти цього:

public class RestTemplateCustomizers {
    static public class MaxConnectionTimeCustomizer implements RestTemplateCustomizer {

        @Override
        public void customize(RestTemplate restTemplate) {
            HttpClient httpClient = HttpClientBuilder
                .create()
                .setConnectionTimeToLive(1000, TimeUnit.MILLISECONDS)
                .build();

            restTemplate.setRequestFactory(
                new HttpComponentsClientHttpRequestFactory(httpClient));
        }
    }
}

// In your service that uses a RestTemplate
public MyRestService(RestTemplateBuilder builder ) {
    restTemplate = builder
         .customizers(new RestTemplateCustomizers.MaxConnectionTimeCustomizer())
         .build();
}

У моєму випадку я отримую цей виняток у кожному 4-му чи 5-му безперервному запиті, а не в 1-му запиті. Будь-яка ідея, що може бути причиною в моєму випадку?
Sahil Chhabra

+1 Хороша, детальна відповідь, але ... IMHO це швидше обхідний шлях для несправної поведінки Apache HttpClient. Правильним рішенням буде перехід на бібліотеку, яка може витончено відновитись після застарілого з'єднання (наприклад, OkHttp).
Г.Демецький

3

Хоча прийнята відповідь є правильною, але IMHO - це лише обхідний шлях.

Щоб бути зрозумілим: це цілком нормальна ситуація, коли постійний зв’язок може застаріти. Але, на жаль, це дуже погано, коли клієнтська бібліотека HTTP не справляється з цим належним чином.

Оскільки ця несправна поведінка в Apache HttpClient не була виправлена ​​протягом багатьох років, я, безумовно, віддав би перевагу переходу на бібліотеку, яка може легко відновитись із застарілої проблеми з підключенням, наприклад, OkHttp.

Чому?

  1. OkHttp за замовчуванням об'єднує http-з'єднання.
  2. Він витончено відновлюється з ситуацій, коли з’єднання http стає застарілим, і запит не може бути повторений через неімпотентність (наприклад, POST). Я не можу сказати цього про Apache HttpClient (згаданий NoHttpResponseException).
  3. Підтримує HTTP / 2.0 з ранніх версій та бета-версій.

Коли я перейшов на OkHttp, мої проблеми з NoHttpResponseExceptionназавжди зникли.


3

Рішення : змініть стратегію повторного використання на ніколи

Оскільки ця проблема дуже складна, і існує так багато різних факторів, які можуть зазнати невдачі, я був радий знайти це рішення в іншому дописі: Як вирішити org.apache.http.NoHttpResponseException

Ніколи не використовуйте підключення повторно: налаштуйте в org.apache.http.impl.client.AbstractHttpClient:

httpClient.setReuseStrategy(new NoConnectionReuseStrategy());

Те саме можна налаштувати у конструкторі org.apache.http.impl.client.HttpClientBuilder:

builder.setConnectionReuseStrategy(new NoConnectionReuseStrategy());

2

Це може статися, якщо disableContentCompression()встановлено на диспетчері пулів, призначеному вашому HttpClient, а цільовий сервер намагається використовувати стиснення gzip.


Ви мені дуже допомогли своїм коментарем. У мене була та сама проблема, але з Spring Cloud Zuul 1.3.2.RELEASE і під великим навантаженням Моє рішення полягало в копіюванні org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilterта видаленні disableContentCompressionвиклику. Я не змінював версію залежності, тому що з баночками є безлад
Артем Птушкін

1

Та сама проблема для мене на клієнті apache http 4.5.5, додаючи заголовок за замовчуванням

З'єднання: тісне

вирішити проблему


5
: facepalm: це неймовірно погана ідея. Постійні зв’язки у світі HTTP були введені з певною метою. Відмова від них лише тому, що деякі клієнтські бібліотеки не можуть їх правильно використовувати, є величезною помилкою.
Г.Демецький

-2

Я зіткнувся з тією ж проблемою, яку вирішив, додавши "connection: close" як розширення,

Крок 1: Створіть новий клас ConnectionCloseExtension

import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.extension.ResponseTransformer;
import com.github.tomakehurst.wiremock.http.HttpHeader;
import com.github.tomakehurst.wiremock.http.HttpHeaders;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.Response;

public class ConnectionCloseExtension extends ResponseTransformer {
  @Override
  public Response transform(Request request, Response response, FileSource files, Parameters parameters) {
    return Response.Builder
        .like(response)
        .headers(HttpHeaders.copyOf(response.getHeaders())
            .plus(new HttpHeader("Connection", "Close")))
        .build();
  }

  @Override
  public String getName() {
    return "ConnectionCloseExtension";
  }
}

Крок 2: встановіть клас розширення в wireMockServer, як показано нижче,

final WireMockServer wireMockServer = new WireMockServer(options()
                .extensions(ConnectionCloseExtension.class)
                .port(httpPort));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.