Erforschung des neuen HTTP-Clients in Java 9

1. Einführung

In diesem Lernprogramm werden wir das neue Inkubationssystem von Java 9 kennenlernen.

Bis vor kurzem hatte Java nur die HttpURLConnection -API zur Verfügung gestellt - eine Low-Level-Version, die nicht für ihre benutzerfreundlichen Funktionen und bekannt ist.

Daher wurden häufig einige weit verbreitete Bibliotheken von Drittanbietern verwendet, z. current/http-client-api.html[Jetty]und Spring’s Link:/rest-template[RestTemplate].

2. Ersteinrichtung

  • Das HTTP-Client-Modul ist als Inkubator-Modul ** in JDK 9 enthalten und unterstützt HTTP/2 , wobei Abwärtskompatibilität noch HTTP/1.1 ermöglicht.

Um es verwenden zu können, müssen wir unser Modul mit einer module-info.java -Datei definieren, die auch das erforderliche Modul zum Ausführen unserer Anwendung angibt:

module com.baeldung.java9.httpclient {
  requires jdk.incubator.httpclient;
}

3. HTTP-Client-API - Übersicht

Im Gegensatz zu HttpURLConnection bietet der HTTP-Client synchrone und asynchrone Anforderungsmechanismen.

Die API besteht aus 3 Kernklassen:

  • HttpRequest - steht für die Anforderung, die über das gesendet werden soll

HttpClient HttpClient - ** verhält sich als Container für Konfigurationsinformationen

häufig bei mehreren Anfragen HttpResponse - ** steht für das Ergebnis eines HttpRequest -Aufrufs

In den folgenden Abschnitten werden wir alle näher untersuchen.

Zuerst konzentrieren wir uns auf eine Anfrage.

4. HttpRequest

HttpRequest, als Name suggests, ist ein Objekt, das die Anfrage darstellt, die wir senden möchten. Neue Instanzen können mit HttpRequest.Builder. erstellt werden.

Wir können es erhalten, indem Sie HttpRequest.newBuilder () aufrufen. Die Builder -Klasse bietet eine Reihe von Methoden, mit denen wir unsere Anfrage konfigurieren können.

Wir werden die wichtigsten behandeln.

4.1. URI einstellen

Das erste, was wir beim Erstellen einer Anfrage tun müssen, ist die URL anzugeben.

Dies kann auf zwei Arten erfolgen: durch Verwendung des Konstruktors für Builder mit dem Parameter URI oder durch Aufrufen der Methode uri (URI) für die Builder -Instanz:

HttpRequest.newBuilder(new URI("https://postman-echo.com/get"))

HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))

Das letzte, was wir zum Erstellen einer Basisanfrage konfigurieren müssen, ist eine HTTP-Methode.

4.2. Angabe der HTTP-Methode

Wir können die HTTP-Methode definieren, die unsere Anfrage verwenden soll, indem Sie eine der Methoden von Builder aufrufen:

  • ERHALTEN()

  • POST (BodyProcessor body)

  • PUT (BodyProcessor body)

  • DELETE (BodyProcessor-Körper)

Wir werden BodyProcessor später ausführlicher behandeln. Nun erstellen wir einfach ein einfaches Beispiel für eine GET-Anfrage ** :

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .GET()
  .build();

Diese Anforderung enthält alle von HttpClient erforderlichen Parameter. Manchmal müssen wir unserer Anfrage jedoch zusätzliche Parameter hinzufügen. Hier sind einige wichtige:

  • die Version des HTTP-Protokolls

  • Überschriften

  • eine Auszeit

4.3. HTTP-Protokollversion einstellen

Die API nutzt das HTTP/2-Protokoll vollständig und verwendet es standardmäßig, aber wir können festlegen, welche Protokollversion wir verwenden möchten.

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .version(HttpClient.Version.HTTP__2)
  .GET()
  .build();
  • Wichtig ist hier zu erwähnen, dass der Client beispielsweise auf HTTP/1.1 zurückgreift, wenn HTTP/2 nicht unterstützt wird. **

4.4. Header setzen

Wenn wir unserer Anfrage zusätzliche Header hinzufügen möchten, können wir die bereitgestellten Builder-Methoden verwenden.

Wir können das auf zwei Arten tun:

  • alle Header als Schlüssel-Wert-Paare an die headers () -Methode oder per übergeben

  • using header () Methode für den einzelnen Schlüsselwert-Header:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .headers("key1", "value1", "key2", "value2")
  .GET()
  .build();

HttpRequest request2 = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .header("key1", "value1")
  .header("key2", "value2")
  .GET()
  .build();

Die letzte nützliche Methode zum Anpassen unserer Anfrage ist timeout () .

4.5. Timeout einstellen

Nun definieren wir die Zeit, die wir auf eine Antwort warten möchten.

Wenn die festgelegte Zeit abläuft, wird eine HttpTimeoutException ausgelöst. Das Standard-Timeout ist auf unendlich gesetzt.

Das Timeout kann mit dem Duration -Objekt festgelegt werden, indem die Methode timeout () in der Builder-Instanz aufgerufen wird:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .timeout(Duration.of(10, SECONDS))
  .GET()
  .build();

5. Festlegen eines Anfragetextes

Wir können einer Anfrage einen Body hinzufügen, indem Sie die Request Builder-Methoden verwenden:

POST (BodyProcessor-Body) , PUT (BodyProcessor-Body) und DELETE (BodyProcessor-Body) .

Die neue API bietet eine Reihe von BodyProcessor -Implementierungen, die die Weitergabe des Anfragetexts vereinfachen:

  • StringProcessor (liest den Rumpf eines String , erstellt mit

HttpRequest.BodyProcessor.fromString ) ** InputStreamProcessor (liest den Body von einem InputStream , erstellt mit

HttpRequest.BodyProcessor.fromInputStream ) ** ByteArrayProcessor (liest den Body aus einem Bytearray, erstellt mit

HttpRequest.BodyProcessor.fromByteArray ) ** FileProcessor (liest den Body aus einer Datei am angegebenen Pfad, erstellt mit

HttpRequest.BodyProcessor.fromFile )

Falls wir keinen Körper brauchen, können wir einfach einen HttpRequest.noBody () übergeben:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .POST(HttpRequest.noBody())
  .build();

5.1. StringBodyProcessor

Das Festlegen eines Anfragetextes mit einer beliebigen BodyProcessor -Implementierung ist sehr einfach und intuitiv.

Wenn Sie beispielsweise den einfachen String als Body übergeben möchten, können Sie StringBodyProcessor verwenden.

Wie bereits erwähnt, kann dieses Objekt mit einer Factory-Methode erstellt werden. FromString () ; es nimmt nur ein String -Objekt als Argument und erstellt daraus einen Körper:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor.fromString("Sample request body"))
  .build();

5.2. InputStreamBodyProcessor

Um dies zu tun, muss InputStream als Supplier übergeben werden (um die Erstellung faul zu gestalten), es ist also etwas anders als oben beschrieben.

Dies ist jedoch auch ganz einfach:

byte[]sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor
   .fromInputStream(() -> new ByteArrayInputStream(sampleData)))
  .build();

Beachten Sie, wie wir hier einen einfachen ByteArrayInputStream verwendet haben. Das kann natürlich jede InputStream -Implementierung sein.

5.3. ByteArrayProcessor

Wir können auch ByteArrayProcessor verwenden und ein Byte-Array als Parameter übergeben:

byte[]sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor.fromByteArray(sampleData))
  .build();

5.4. FileProcessor

Um mit einer Datei zu arbeiten, können wir den bereitgestellten FileProcessor verwenden. Die Factory-Methode nimmt einen Pfad zu der Datei als Parameter und erstellt aus dem Inhalt einen Körper:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor.fromFile(
    Paths.get("src/test/resources/sample.txt")))
  .build();

Wir haben behandelt, wie Sie HttpRequest erstellen und zusätzliche Parameter festlegen.

Nun ist es an der Zeit, die HttpClient -Klasse genauer zu untersuchen, die für das Senden von Anforderungen und das Empfangen von Antworten verantwortlich ist.

6. HttpClient

Alle Anforderungen werden mit HttpClient gesendet, das mit der Methode HttpClient.newBuilder () oder durch Aufruf von HttpClient.newHttpClient () instanziiert werden kann.

Es bietet viele nützliche und selbstbeschreibende Methoden, mit denen wir unsere Anfrage/Antwort bearbeiten können.

Lassen Sie uns einige davon hier behandeln.

6.1. Proxy einstellen

Wir können einen Proxy für die Verbindung definieren. Rufen Sie einfach die proxy () - Methode für eine Builder -Instanz auf:

HttpResponse<String> response = HttpClient
  .newBuilder()
  .proxy(ProxySelector.getDefault())
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

In unserem Beispiel haben wir den Standard-System-Proxy verwendet.

6.2. Festlegen der Umleitungsrichtlinie

Manchmal wurde die Seite, auf die wir zugreifen möchten, an eine andere Adresse verschoben.

In diesem Fall erhalten wir den HTTP-Statuscode 3xx, in der Regel mit den Informationen über den neuen URI. HttpClient kann die Anforderung automatisch an den neuen URI umleiten, wenn die entsprechende Umleitungsrichtlinie festgelegt wird.

Wir können dies mit der followRedirects () Methode in Builder machen:

HttpResponse<String> response = HttpClient.newBuilder()
  .followRedirects(HttpClient.Redirect.ALWAYS)
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

Alle Richtlinien werden in enum HttpClient.Redirect definiert und beschrieben.

6.3. Authenticator für eine Verbindung festlegen

Ein Authenticator ist ein Objekt, das Anmeldeinformationen (HTTP-Authentifizierung) für eine Verbindung aushandelt.

Es bietet verschiedene Authentifizierungsschemata (wie z. B. Basis- oder Digest-Authentifizierung). In den meisten Fällen erfordert die Authentifizierung einen Benutzernamen und ein Kennwort, um eine Verbindung zu einem Server herzustellen.

Wir können die PasswordAuthentication -Klasse verwenden, die nur diese Werte enthält:

HttpResponse<String> response = HttpClient.newBuilder()
  .authenticator(new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
      return new PasswordAuthentication(
        "username",
        "password".toCharArray());
    }
}).build()
  .send(request, HttpResponse.BodyHandler.asString());

Im obigen Beispiel haben wir den Benutzernamen und das Kennwort als Klartext übergeben. In einem Produktionsszenario muss dies natürlich anders sein.

Beachten Sie, dass nicht jede Anfrage denselben Benutzernamen und dasselbe Kennwort verwenden muss.

Die Authenticator -Klasse stellt eine Reihe von getXXX -Methoden (z. B. getRequestingSite () ) -Methoden bereit, mit denen ermittelt werden kann, welche Werte bereitgestellt werden sollen.

Jetzt werden wir eine der nützlichsten Funktionen der neuen HttpClient -asynchronen Aufrufe an den Server untersuchen.

6.4. Sendeanfragen - Synchronisierung vs. Async

Der neue HttpClient bietet zwei Möglichkeiten, eine Anfrage an einen Server zu senden:

  • send (…​) - synchron (blockiert bis die Antwort kommt)

  • sendAsync (…​) - asynchron (wartet nicht auf Antwort,

nicht blockierend)

Bisher wartet die Methode send (…​) natürlich auf eine Antwort:

HttpResponse<String> response = HttpClient.newBuilder()
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

Dieser Aufruf gibt ein HttpResponse -Objekt zurück. Wir sind sicher, dass die nächste Anweisung aus unserem Anwendungsablauf nur ausgeführt wird, wenn die Antwort bereits hier ist.

Es hat jedoch viele Nachteile, besonders wenn wir große Datenmengen verarbeiten.

Jetzt können wir die Methode sendAsync (…​) verwenden. Diese Methode gibt CompletableFeature <HttpResponse> - zurück, um eine Anforderung asynchron zu verarbeiten. :

CompletableFuture<HttpResponse<String>> response = HttpClient.newBuilder()
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

Die neue API kann auch mehrere Antworten verarbeiten und die Anfrage- und Antwortkörper streamen:

List<URI> targets = Arrays.asList(
  new URI("https://postman-echo.com/get?foo1=bar1"),
  new URI("https://postman-echo.com/get?foo2=bar2"));
HttpClient client = HttpClient.newHttpClient();
List<CompletableFuture<String>> futures = targets.stream()
  .map(target -> client
    .sendAsync(
      HttpRequest.newBuilder(target).GET().build(),
      HttpResponse.BodyHandler.asString())
    .thenApply(response -> response.body()))
  .collect(Collectors.toList());

6.5. Executor für asynchrone Aufrufe einstellen

Wir können auch einen Executor definieren, der Threads für asynchrone Aufrufe bereitstellt.

Auf diese Weise können wir beispielsweise die Anzahl der Threads begrenzen, die zur Bearbeitung von Anforderungen verwendet werden:

ExecutorService executorService = Executors.newFixedThreadPool(2);

CompletableFuture<HttpResponse<String>> response1 = HttpClient.newBuilder()
  .executor(executorService)
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

CompletableFuture<HttpResponse<String>> response2 = HttpClient.newBuilder()
  .executor(executorService)
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

Standardmäßig verwendet der HttpClient den Executor java.util.concurrent.Executors.newCachedThreadPool () .

6.6. CookieManager definieren

Mit der neuen API und dem neuen Builder ist es einfach, einen CookieManager für unsere Verbindung festzulegen. Wir können die Builder-Methode cookieManager (CookieManager cookieManager) verwenden, um clientspezifische CookieManager zu definieren.

Definieren Sie zum Beispiel CookieManager , wodurch überhaupt keine Cookies akzeptiert werden können:

HttpClient.newBuilder()
  .cookieManager(new CookieManager(null, CookiePolicy.ACCEPT__NONE))
  .build();

Wenn unser CookieManager die Speicherung von Cookies erlaubt, können Sie auf sie zugreifen, indem Sie CookieManager in unserem HttpClient überprüfen:

httpClient.cookieManager().get().getCookieStore()

Nun konzentrieren wir uns auf die letzte Klasse aus der HTTP-API - die HttpResponse .

7. HttpResponse Object

Die Klasse HttpResponse repräsentiert die Antwort vom Server. Es bietet eine Reihe nützlicher Methoden - die zwei wichtigsten sind jedoch:

  • statusCode () - gibt den Statuscode (Typ int ) für eine Antwort zurück

( HttpURLConnection -Klasse enthält mögliche Werte) ** body () - gibt einen Körper für eine Antwort zurück (der Rückgabetyp hängt von der

Antwort BodyHandler -Parameter, der an die send () -Methode übergeben wird

Das Antwortobjekt hat eine andere nützliche Methode, die wir behandeln, wie uri () , headers () , trailers () und version () .

7.1. URI des Antwortobjekts

Die Methode uri () des Antwortobjekts gibt den URI zurück, von dem wir die Antwort erhalten haben.

Manchmal kann es im Anforderungsobjekt anders als URI sein, da eine Umleitung auftreten kann:

assertThat(request.uri()
  .toString(), equalTo("http://stackoverflow.com"));
assertThat(response.uri()
  .toString(), equalTo("https://stackoverflow.com/"));

7.2. Header aus Antwort

Wir können Header aus der Antwort erhalten, indem wir die Methode headers () für ein Antwortobjekt aufrufen:

HttpResponse<String> response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandler.asString());
HttpHeaders responseHeaders = response.headers();

Es gibt HttpHeaders object als Rückgabetyp zurück. Dies ist ein neuer Typ, der im Paket jdk.incubator.http definiert ist und eine schreibgeschützte Ansicht der HTTP-Header darstellt.

Es gibt einige nützliche Methoden, die die Suche nach dem Header-Wert vereinfachen.

7.3. Trailer von Response abrufen

Die HTTP-Antwort kann zusätzliche Header enthalten, die nach dem Antwortinhalt enthalten sind. Diese Header werden als Trailer-Header bezeichnet.

Sie können sie erhalten, indem Sie die Methode trailers () auf HttpResponse: aufrufen.

HttpResponse<String> response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandler.asString());
CompletableFuture<HttpHeaders> trailers = response.trailers();

Beachten Sie, dass die Methode trailers () das Objekt CompletableFuture zurückgibt.

7.4. Version der Antwort

Die Methode version () definiert, welche Version des HTTP-Protokolls für die Kommunikation mit einem Server verwendet wurde.

  • Denken Sie daran, dass der Server selbst dann, wenn wir definieren, dass wir HTTP/2 verwenden möchten, über HTTP/1.1 antworten kann. **

Die Version, in der der Server geantwortet hat, ist in der Antwort angegeben:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .version(HttpClient.Version.HTTP__2)
  .GET()
  .build();
HttpResponse<String> response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandler.asString());
assertThat(response.version(), equalTo(HttpClient.Version.HTTP__1__1));

8. Fazit

In diesem Artikel haben wir uns mit der HttpClient -API von Java 9 befasst, die eine Menge Flexibilität und leistungsstarke Funktionen bietet.

Den vollständigen Code finden Sie wie immer unter over auf GitHub .

Hinweis: In den Beispielen haben wir Beispiel-REST-Endpunkte verwendet, die von bereitgestellt werden https://postman-echo.com .