Apache HttpClient Interim Error: NoHttpResponseException

76

Ich habe einen Webservice, der eine POST-Methode mit XML akzeptiert. Es funktioniert einwandfrei, dann kann es gelegentlich nicht mit dem Server kommunizieren, der eine IOException mit einer Nachricht auslöst The target server failed to respond. Die nachfolgenden Aufrufe funktionieren einwandfrei.

Es passiert meistens, wenn ich ein paar Anrufe tätige und dann meine Anwendung für etwa 10-15 Minuten im Leerlauf lasse. Der erste Aufruf, den ich danach tätige, gibt diesen Fehler zurück.

Ich habe ein paar Dinge ausprobiert ...

Ich richte den Retry-Handler wie ein

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);

aber dieser Wiederholungsversuch wurde nie aufgerufen. (Ja, ich verwende die richtige Instanz der Klausel). Beim Debuggen wird diese Klasse niemals aufgerufen.

Ich habe sogar versucht, es einzurichten, HttpProtocolParams.setUseExpectContinue(httpClient.getParams(), false);aber es nützt nichts. Kann jemand vorschlagen, was ich jetzt tun kann?

WICHTIG Neben der Frage, warum ich die Ausnahme bekomme, ist eines der wichtigsten Anliegen, warum der Retryhandler hier nicht arbeitet.

Em Ae
quelle
Ich denke nicht, dass es etwas mit Client-Code ist. Möglicherweise ist der Zielserver zu beschäftigt mit der Verarbeitung von Antworten?
kosa
1
Ich habe versucht, den Zielserver zu bombardieren, aber es hat gut funktioniert. Ich habe sogar versucht, die gleichen Schritte auszuführen, um den Fehler mit Fiddler zu reproduzieren, aber kein Glück!
Em Ae
Auf welcher Art von Webserver wird der Dienst ausgeführt, und während der Wartezeit von 10 bis 15 Minuten erhält der Dienst andere Anforderungen oder ist der Dienst inaktiv?
Shawn
Kater. Nein, es ist ein Schein-Service-Geldautomat und er empfängt nichts anderes als meine Anrufe.
Em Ae
Ich erhalte diesen Fehler ebenfalls, jedoch in einem Wrapper von ResourceAccessException.
Sagar Kharab

Antworten:

115

Höchstwahrscheinlich sind dauerhafte Verbindungen, die vom Verbindungsmanager am Leben erhalten werden, veraltet. Das heißt, der Zielserver beendet die Verbindung an ihrem Ende, ohne dass HttpClient auf dieses Ereignis reagieren kann, während die Verbindung inaktiv ist, wodurch die Verbindung halb geschlossen oder "veraltet" wird. Normalerweise ist dies kein Problem. HttpClient verwendet verschiedene Techniken, um die Gültigkeit der Verbindung nach dem Leasing aus dem Pool zu überprüfen. Selbst wenn die Überprüfung der veralteten Verbindung deaktiviert ist und eine veraltete Verbindung zum Senden einer Anforderungsnachricht verwendet wird, schlägt die Anforderungsausführung normalerweise beim Schreibvorgang mit SocketException fehl und wird automatisch wiederholt. Unter bestimmten Umständen kann der Schreibvorgang jedoch ausnahmslos beendet werden, und der nachfolgende Lesevorgang gibt -1 (Ende des Streams) zurück.

Der einfachste Weg, um Abhilfe zu schaffen, besteht darin, abgelaufene Verbindungen und Verbindungen, die nach einer Zeit der Inaktivität länger als beispielsweise 1 Minute inaktiv waren, aus dem Pool zu entfernen. Weitere Informationen finden Sie in diesem Abschnitt des HttpClient-Tutorials .

ok2c
quelle
Ich verwende HTTTPClient 4.5.1, hier für zwei kontinuierliche Wiederholungsserver fehlgeschlagene Antwort, aber drittes Mal erfolgreich, warum stellt es nicht im ersten Versuch eine Verbindung her, obwohl ich die Zeit 1 Minute wie angegeben gehalten habe.
Kundan Atre
@oleg, Was ist der Unterschied zwischen inaktiven und abgelaufenen Verbindungen?
Ales
1
Einige Verbindungen haben möglicherweise eine Ablaufzeit, ab der sie als nicht mehr gültig / wiederverwendbar angesehen werden sollten. Leerlaufverbindungen sind absolut gültig und wiederverwendbar, werden jedoch momentan nicht verwendet.
ok2c
2
Es hängt von vielen Faktoren ab. Es sollte für sichere und idempotente Methoden sicher sein, solange der Server in Bezug auf Methodensicherheit und Idempotenz der HTTP-Spezifikation entspricht.
ok2c
1
Die Verwendung der hier empfohlenen Zeitüberschreitung von einer Minute war in meinem Fall genau die Problemursache: Der Apache-Client spricht mit dem lokalen Jetty-Server. AFAIK Standardmäßig schließt der Steg die Verbindungen nach 30 Sekunden Inaktivität. Das Einstellen connectionManager.setValidateAfterInactivity(500), dh eine halbe Sekunde, löste mein Problem. Ich denke, ein paar Sekunden würden genauso gut funktionieren, aber es ist mir egal.
Maaartinus
20

Akzeptierte Antwort ist richtig, aber es fehlt die Lösung. Um diesen Fehler zu vermeiden, können Sie setHttpRequestRetryHandler (oder setRetryHandler für Apache-Komponenten 4.4) für Ihren HTTP-Client wie in dieser Antwort hinzufügen .

Jehy
quelle
3
Ist ein Wiederholungshandler hier der richtige Ansatz, da in der ursprünglichen Frage ein POST angegeben ist?
Brian Agnew
2
Fragen Sie, ob ein POST sicher erneut versucht werden kann? Als Entwurfsprinzip ist ein PUT idempotent. Das Wiederholen eines POST kann unbeabsichtigte Folgen haben. Andererseits werden nicht alle PUTs idempotent geschrieben.
Activedecay
Es ist nahezu sicher, POST-Anforderungen mit der von mir angegebenen Methode erneut zu versuchen, da nur die NoHttpResponseException verarbeitet und erneut versucht wird, die aufgrund der Clientseite und nicht aufgrund der Serverseite viel besser möglich ist.
Jehy
9
@Jehy Es ist überhaupt nicht absolut sicher ... NoHttpResponseException bedeutet, dass der Client keine Antwort erhalten hat, nicht, dass der Server die Anfrage nicht erhalten und / oder verarbeitet hat
Lyrkan
Die Sicherheit könnte durch die Implementierung gewährleistet werden. Wenn Inhaltsteile während der Schlüssel- / Identitätserstellung wiederverwendet werden, könnte der Server 400 ausspucken und erkennen, dass der Eintrag bereits erstellt wurde.
Aubergine
7

HttpClient 4.4 hatte in diesem Bereich einen Fehler im Zusammenhang mit der Überprüfung möglicherweise veralteter Verbindungen, bevor es zum Anforderer zurückkehrte. Es wurde nicht überprüft, ob eine Verbindung veraltet war, und dies führt dann zu einer sofortigen Verbindung NoHttpResponseException.

Dieses Problem wurde in HttpClient 4.4.1 behoben. Siehe diesen JIRA und die Versionshinweise

Brian Agnew
quelle
14
Ich verwende die Version 4.5.3, erhalte aber immer noch NoHttpResponseException . In meinem Fall erhalte ich diese Ausnahme bei jeder 4. oder 5. fortlaufenden Anfrage, nicht bei der 1. Anfrage. Irgendeine Idee, was der Grund in meinem Fall sein könnte?
Sahil Chhabra
1
@ SahilChhabra Gleiches Szenario an meiner Seite. Konnten Sie eine Lösung für Ihr Problem finden?
Manish Bansal
4

Heutzutage werden die meisten HTTP-Verbindungen als dauerhaft angesehen, sofern nicht anders angegeben . Um Serverressourcen zu sparen, wird die Verbindung jedoch selten für immer offen gehalten. Das Standard-Verbindungszeitlimit für viele Server ist eher kurz, z. B. 5 Sekunden für Apache httpd 2.2 und höher.

Der org.apache.http.NoHttpResponseExceptionFehler ist höchstwahrscheinlich auf eine dauerhafte Verbindung zurückzuführen, die vom Server geschlossen wurde.

Es ist möglich, die maximale Zeit in Millisekunden festzulegen, um nicht verwendete Verbindungen im Apache Http-Clientpool offen zu halten.

Mit Spring Boot können Sie dies auf folgende Weise erreichen:

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();
}
Berthier Lemieux
quelle
In meinem Fall erhalte ich diese Ausnahme bei jeder 4. oder 5. fortlaufenden Anfrage, nicht bei der 1. Anfrage. Irgendeine Idee, was der Grund in meinem Fall sein könnte?
Sahil Chhabra
+1 Gute, detaillierte Antwort, aber ... IMHO ist es eher eine Problemumgehung für das fehlerhafte Verhalten von Apache HttpClient. Die richtige Lösung wäre, zu einer Bibliothek zu wechseln, die sich ordnungsgemäß von einer veralteten Verbindung (z. B. OkHttp) erholen kann.
G. Demecki
3

Die akzeptierte Antwort ist zwar richtig, aber IMHO ist nur eine Problemumgehung.

Um es klar auszudrücken: Es ist eine ganz normale Situation, dass eine dauerhafte Verbindung veraltet sein kann. Leider ist es sehr schlecht, wenn die HTTP-Clientbibliothek nicht richtig damit umgehen kann.

Da dieses fehlerhafte Verhalten in Apache HttpClient viele Jahre lang nicht behoben wurde, würde ich definitiv lieber zu einer Bibliothek wechseln, die sich leicht von einem veralteten Verbindungsproblem erholen lässt, z. B. OkHttp.

Warum?

  1. OkHttp bündelt standardmäßig http-Verbindungen.
  2. Es wird ordnungsgemäß von Situationen wiederhergestellt, in denen die http-Verbindung veraltet ist und die Anforderung nicht wiederholt werden kann, da sie nicht idempotent ist (z. B. POST). Ich kann es nicht über Apache HttpClient (erwähnt NoHttpResponseException) sagen .
  3. Unterstützt HTTP / 2.0 aus frühen Entwürfen und Beta-Versionen.

Als ich zu OkHttp wechselte, verschwanden meine Probleme NoHttpResponseExceptionfür immer.

G. Demecki
quelle
3

Lösung : Ändern Sie die ReuseStrategy in nie

Da dieses Problem sehr komplex ist und es so viele verschiedene Faktoren gibt, die fehlschlagen können, war ich froh, diese Lösung in einem anderen Beitrag zu finden: So lösen Sie org.apache.http.NoHttpResponseException

Verbindungen niemals wiederverwenden: Konfigurieren in org.apache.http.impl.client.AbstractHttpClient:

httpClient.setReuseStrategy(new NoConnectionReuseStrategy());

Dasselbe kann auf einem org.apache.http.impl.client.HttpClientBuilder-Builder konfiguriert werden:

builder.setConnectionReuseStrategy(new NoConnectionReuseStrategy());
Anti-g
quelle
2

Dies kann passieren, wenn dies disableContentCompression()auf einem Pooling-Manager festgelegt ist, der Ihrem HttpClient zugewiesen ist, und der Zielserver versucht, die gzip-Komprimierung zu verwenden.

Amalgovinus
quelle
Sie haben mir mit Ihrem Kommentar sehr geholfen. Ich hatte das gleiche Problem wie dieses, aber mit Spring Cloud Zuul 1.3.2.RELEASE und unter hoher Last. Meine Lösung bestand darin, Anrufe zu kopieren, einzufügen und zu org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilterentfernen. disableContentCompressionIch habe die Abhängigkeitsversion nicht geändert, da es ein ziemliches Durcheinander mit Gläsern gibt
Artem Ptushkin
1

Gleiches Problem für mich auf Apache http Client 4.5.5 Hinzufügen von Standard-Header

Verbindung: schließen

das Problem lösen

Salvatore Napoli
quelle
5
: facepalm: das ist eine unglaublich schlechte idee. Persistente Verbindungen in der HTTP-Welt wurden zu einem bestimmten Zweck eingeführt. Es ist ein großer Fehler, sie aufzugeben, nur weil einige Client-Bibliotheken sie nicht richtig verwenden können.
G. Demecki
-2

Ich habe das gleiche Problem, das ich durch Hinzufügen von "connection: close" als Erweiterung gelöst habe.

Schritt 1: Erstellen Sie eine neue Klasse 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";
  }
}

Schritt 2: Legen Sie die Erweiterungsklasse in wireMockServer wie folgt fest:

final WireMockServer wireMockServer = new WireMockServer(options()
                .extensions(ConnectionCloseExtension.class)
                .port(httpPort));
Chandrahasan
quelle