Asynchrones HTTP mit asynchronem http-Client in Java

1. Übersicht

AsyncHttpClient (AHC) ist eine Bibliothek, die auf Netty aufbaut, um HTTP-Anforderungen einfach auszuführen und Antworten asynchron zu verarbeiten.

In diesem Artikel wird erläutert, wie Sie den HTTP-Client konfigurieren und verwenden, eine Anforderung ausführen und die Antwort mithilfe von AHC verarbeiten.

2. Setup

Die neueste Version der Bibliothek befindet sich im Maven-Repository. Wir sollten darauf achten, die Abhängigkeit mit der Gruppen-ID org.asynchttpclient zu verwenden und nicht die mit com.ning:

 org.asynchttpclient async-http-client 2.2.0 

3. HTTP-Client-Konfiguration

Die einfachste Methode zum Abrufen des HTTP-Clients ist die Verwendung der Dsl- Klasse. Die statische asyncHttpClient () -Methode gibt ein AsyncHttpClient- Objekt zurück:

AsyncHttpClient client = Dsl.asyncHttpClient();

Wenn wir eine benutzerdefinierte Konfiguration des HTTP-Clients benötigen, können wir das AsyncHttpClient- Objekt mit dem Builder DefaultAsyncHttpClientConfig.Builder erstellen :

DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config()

Dies bietet die Möglichkeit, Zeitüberschreitungen, einen Proxyserver, HTTP-Zertifikate und vieles mehr zu konfigurieren:

DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config() .setConnectTimeout(500) .setProxyServer(new ProxyServer(...)); AsyncHttpClient client = Dsl.asyncHttpClient(clientBuilder);

Sobald wir eine Instanz des HTTP-Clients konfiguriert und erhalten haben, können wir sie anwendungsübergreifend wiederverwenden . Wir müssen nicht für jede Anforderung eine Instanz erstellen, da intern neue Threads und Verbindungspools erstellt werden, was zu Leistungsproblemen führt.

Es ist auch wichtig zu beachten, dass wir nach Abschluss der Verwendung des Clients die Methode close () aufrufen sollten , um Speicherverluste oder hängende Ressourcen zu vermeiden .

4. Erstellen einer HTTP-Anforderung

Es gibt zwei Methoden, mit denen wir eine HTTP-Anforderung mithilfe von AHC definieren können:

  • gebunden
  • ungebunden

Es gibt keinen wesentlichen Unterschied zwischen den beiden Anforderungstypen hinsichtlich der Leistung. Sie stellen nur zwei separate APIs dar, mit denen wir eine Anforderung definieren können. Eine gebundene Anforderung ist an den HTTP-Client gebunden, von dem aus sie erstellt wurde, und verwendet standardmäßig die Konfiguration dieses bestimmten Clients, sofern nicht anders angegeben.

Wenn Sie beispielsweise eine gebundene Anforderung erstellen, wird das Flag disableUrlEncoding aus der HTTP-Client-Konfiguration gelesen, während dies für eine ungebundene Anforderung standardmäßig auf false festgelegt ist. Dies ist nützlich, da die Clientkonfiguration geändert werden kann, ohne die gesamte Anwendung neu zu kompilieren, indem Systemeigenschaften verwendet werden, die als VM-Argumente übergeben werden:

java -jar -Dorg.asynchttpclient.disableUrlEncodingForBoundRequests=true

Eine vollständige Liste der Eigenschaften finden Sie in der Datei ahc-default.properties .

4.1. Gebundene Anfrage

Um eine gebundene Anfrage zu erstellen, verwenden wir die Hilfsmethoden aus der Klasse AsyncHttpClient , die mit dem Präfix "prepare" beginnen . Wir können auch die prepareRequest () -Methode verwenden, die ein bereits erstelltes Request- Objekt empfängt .

Beispielsweise erstellt die Methode prepareGet () eine HTTP-GET-Anforderung:

BoundRequestBuilder getRequest = client.prepareGet("//www.baeldung.com");

4.2. Ungebundene Anfrage

Eine ungebundene Anforderung kann mit der RequestBuilder- Klasse erstellt werden:

Request getRequest = new RequestBuilder(HttpConstants.Methods.GET) .setUrl("//www.baeldung.com") .build();

oder mithilfe der Dsl-Hilfsklasse , die den RequestBuilder tatsächlich zum Konfigurieren der HTTP-Methode und der URL der Anforderung verwendet:

Request getRequest = Dsl.get("//www.baeldung.com").build()

5. Ausführen von HTTP-Anforderungen

Der Name der Bibliothek gibt uns einen Hinweis darauf, wie die Anforderungen ausgeführt werden können. AHC unterstützt sowohl synchrone als auch asynchrone Anforderungen.

Die Ausführung der Anforderung hängt von ihrem Typ ab. Wenn Sie eine gebundene Anforderung verwenden, verwenden wir die execute () -Methode aus der BoundRequestBuilder- Klasse. Wenn wir eine ungebundene Anforderung haben, führen wir sie mit einer der Implementierungen der executeRequest () -Methode aus der AsyncHttpClient- Schnittstelle aus .

5.1. Synchron

Die Bibliothek wurde asynchron entworfen, aber bei Bedarf können wir synchrone Aufrufe simulieren, indem wir das Future- Objekt blockieren . Die Methoden execute () und executeRequest () geben ein ListenableFuture- Objekt zurück. Diese Klasse erweitert die Java Future- Schnittstelle und erbt damit die Methode get () , mit der der aktuelle Thread blockiert werden kann, bis die HTTP-Anforderung abgeschlossen ist und eine Antwort zurückgegeben wird:

Future responseFuture = boundGetRequest.execute(); responseFuture.get();
Future responseFuture = client.executeRequest(unboundRequest); responseFuture.get();

Die Verwendung synchroner Aufrufe ist nützlich, wenn Sie versuchen, Teile unseres Codes zu debuggen. Es wird jedoch nicht empfohlen, sie in einer Produktionsumgebung zu verwenden, in der asynchrone Ausführungen zu einer besseren Leistung und einem besseren Durchsatz führen.

5.2. Asynchron

When we talk about asynchronous executions, we also talk about listeners for processing the results. The AHC library provides 3 types of listeners that can be used for asynchronous HTTP calls:

  • AsyncHandler
  • AsyncCompletionHandler
  • ListenableFuture listeners

The AsyncHandler listener offers the possibility to control and process the HTTP call before it has completed. Using it can handle a series of events related to the HTTP call:

request.execute(new AsyncHandler() { @Override public State onStatusReceived(HttpResponseStatus responseStatus) throws Exception { return null; } @Override public State onHeadersReceived(HttpHeaders headers) throws Exception { return null; } @Override public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { return null; } @Override public void onThrowable(Throwable t) { } @Override public Object onCompleted() throws Exception { return null; } });

The State enum lets us control the processing of the HTTP request. By returning State.ABORT we can stop the processing at a specific moment and by using State.CONTINUE we let the processing finish.

It's important to mention that the AsyncHandler isn't thread-safe and shouldn't be reused when executing concurrent requests.

AsyncCompletionHandler inherits all the methods from the AsyncHandler interface and adds the onCompleted(Response) helper method for handling the call completion. All the other listener methods are overridden to return State.CONTINUE, thus making the code more readable:

request.execute(new AsyncCompletionHandler() { @Override public Object onCompleted(Response response) throws Exception { return response; } });

The ListenableFuture interface lets us add listeners that will run when the HTTP call is completed.

Also, it let's execute the code from the listeners – by using another thread pool:

ListenableFuture listenableFuture = client .executeRequest(unboundRequest); listenableFuture.addListener(() -> { Response response = listenableFuture.get(); LOG.debug(response.getStatusCode()); }, Executors.newCachedThreadPool());

Mit der Option zum Hinzufügen von Listenern können wir über die ListenableFuture- Oberfläche die Future- Antwort in eine CompletableFuture umwandeln .

7. Fazit

AHC ist eine sehr leistungsfähige Bibliothek mit vielen interessanten Funktionen. Es bietet eine sehr einfache Möglichkeit, einen HTTP-Client zu konfigurieren und sowohl synchrone als auch asynchrone Anforderungen auszuführen.

Wie immer ist der Quellcode für den Artikel auf GitHub verfügbar.