Udforskning af den nye HTTP-klient i Java

1. Introduktion

I denne vejledning udforsker vi Java 9s nye inkubering HttpClient.

Indtil for nylig leverede Java kun HttpURLforbindelse API - som er lavt niveau og ikke er kendt for at være funktionsrigogbrugervenlig.

Derfor blev nogle almindeligt anvendte tredjepartsbiblioteker almindeligt anvendt - såsom Apache HttpClient, Jetty og Spring's RestTemplate.

2. Første opsætning

HTTP-klientmodulet er samlet som et inkubatormodul i JDK 9 og understøtter HTTP / 2 med bagudkompatibilitet, der stadig letter HTTP / 1.1.

For at bruge det skal vi definere vores modul ved hjælp af en modul-info.java fil, der også angiver det krævede modul til at køre vores applikation:

modul com.baeldung.java9.httpclient {kræver jdk.incubator.httpclient; }

3. HTTP Client API-oversigt

I modsætning til HttpURLforbindelse, HTTP-klient giver synkron og asynkron anmodningsmekanismer.

API'en består af 3 kerneklasser:

  • HttpForespørgselrepræsenterer anmodningen om at blive sendt via HttpClient
  • HttpClientopfører sig som en container til konfigurationsoplysninger, der er fælles for flere anmodninger
  • HttpResponserepræsenterer resultatet af en HttpForespørgsel opkald

Vi undersøger hver af dem mere detaljeret i de følgende afsnit. Lad os først fokusere på en anmodning.

4. HttpForespørgsel

HttpForespørgsel, som navnet foreslår, er et objekt, der repræsenterer anmodning, vi vil sende. Nye forekomster kan oprettes ved hjælp af HttpRequest.Builder.

Vi kan få det ved at ringe HttpRequest.newBuilder (). Bygger klasse giver en masse metoder, som vi kan bruge til at konfigurere vores anmodning.

Vi dækker de vigtigste.

4.1. Indstilling URI

Den første ting, vi skal gøre, når vi opretter en anmodning, er at angive URL'en.

Vi kan gøre det på to måder - ved at bruge konstruktøren til Bygger med URI parameter eller ved kaldemetode uri (URI) på den Bygger eksempel:

HttpRequest.newBuilder (ny URI ("// postman-echo.com/get")) HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get"))

Den sidste ting, vi skal konfigurere for at oprette en grundlæggende anmodning, er en HTTP-metode.

4.2. Angivelse af HTTP-metoden

Vi kan definere den HTTP-metode, som vores anmodning vil bruge, ved at kalde en af ​​metoderne fra Bygger:

  • FÅ()
  • POST (BodyProcessor body)
  • PUT (BodyProcessor body)
  • SLET (BodyProcessor-krop)

Vi dækker BodyProcessor i detaljer senere. Lad os nu bare oprette et meget simpelt eksempel på GET-anmodning:

HttpRequest anmodning = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get")) .GET () .build ();

Denne anmodning har alle parametre, der kræves af HttpClient. Imidlertid er vi undertiden nødt til at tilføje yderligere parametre til vores anmodning; her er nogle vigtige er:

  • versionen af ​​HTTP-protokollen
  • overskrifter
  • en timeout

4.3. Indstilling af HTTP-protokolversion

API udnytter fuldt ud HTTP / 2-protokollen og bruger den som standard, men vi kan definere, hvilken version af protokollen vi vil bruge.

HttpRequest anmodning = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get")) .version (HttpClient.Version.HTTP_2) .GET () .build ();

Vigtigt at nævne her er, at klienten falder tilbage til f.eks. HTTP / 1.1, hvis HTTP / 2 ikke understøttes.

4.4. Indstilling af overskrifter

Hvis vi vil tilføje yderligere overskrifter til vores anmodning, kan vi bruge de medfølgende bygningsmetoder.

Vi kan gøre det på en af ​​to måder:

  • videregive alle overskrifter som nøgleværdipar til overskrifter () metode eller ved
  • ved brug af header() metode til den enkelte nøgle-værdi overskrift:
HttpRequest anmodning = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get")). Headers ("key1", "value1", "key2", "value2") .GET () .build (); HttpRequest anmodning2 = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get")). Header ("key1", "value1"). Header ("key2", "value2"). GET () .build (); 

Den sidste nyttige metode, vi kan bruge til at tilpasse vores anmodning, er en timeout ().

4.5. Indstilling af en timeout

Lad os nu definere, hvor lang tid vi vil vente på et svar.

Hvis den indstillede tid udløber, a HttpTimeoutException vil blive kastet; standard timeout er indstillet til uendelig.

Timeout kan indstilles med Varighed objekt - ved at kalde metode tiden er gået() på bygherreinstansen:

HttpRequest anmodning = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get")). Timeout (Duration.of (10, SECONDS)) .GET () .build ();

5. Indstilling af et anmodningsorgan

Vi kan føje et organ til en anmodning ved hjælp af metoder til anmodningsbygger: POST (BodyProcessor body), PUT (BodyProcessor body) og SLET (BodyProcessor-krop).

Den nye API giver et antal BodyProcessor implementeringer out-of-the-box, der forenkler overførsel af anmodningsorganet:

  • StringProcessor (læser krop fra en Snor, oprettet med HttpRequest.BodyProcessor.fromString)
  • InputStreamProcessor (læser krop fra en InputStream, oprettet med HttpRequest.BodyProcessor.fromInputStream)
  • ByteArrayProcessor (læser brødtekst fra et byte-array, oprettet med HttpRequest.BodyProcessor.fromByteArray)
  • FileProcessor (læser brødtekst fra en fil på den givne sti, oprettet med HttpRequest.BodyProcessor.fromFile)

Hvis vi ikke har brug for en krop, kan vi simpelthen passere i en HttpRequest.noBody ():

HttpRequest anmodning = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/post")). POST (HttpRequest.noBody ()) .build ();

5.1. StringBodyProcessor

Indstilling af et anmodningsorgan med ethvert BodyProcessor implementering er meget enkel og intuitiv.

For eksempel, hvis vi ønsker at give et simpelt Snor som et legeme kan vi bruge StringBodyProcessor.

Som vi allerede har nævnt, kan dette objekt oprettes med en fabriksmetode fromString (); det tager kun en Snor objekt som et argument og skaber en krop ud fra det:

HttpRequest-anmodning = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/post")). Headers ("Content-Type", "text / plain; charset = UTF-8"). POST (HttpRequest.BodyProcessor.fromString ("Eksempel på anmodningsorgan")) .build (); 

5.2. InputStreamBodyProcessor

For at gøre det skal InputStream skal bestås som en Leverandør (for at gøre skabelsen doven), så det er lidt anderledes end beskrevet ovenfor StringBodyProcessor.

Dette er dog også ret ligetil:

byte [] sampleData = "Eksempel på anmodningens brødtekst" .getBytes (); HttpRequest anmodning = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/post")). Headere ("Content-Type", "text / plain; charset = UTF-8"). POST (HttpRequest.BodyProcessor. FraInputStream (() -> ny ByteArrayInputStream (sampleData))) .build (); 

Læg mærke til, hvordan vi brugte en simpel ByteArrayInputStream her; det kan selvfølgelig være nogen InputStream implementering.

5.3. ByteArrayProcessor

Vi kan også bruge ByteArrayProcessor og videregive en række bytes som parameter:

byte [] sampleData = "Eksempel på anmodningens brødtekst" .getBytes (); HttpRequest-anmodning = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/post")). Headers ("Content-Type", "text / plain; charset = UTF-8"). POST (HttpRequest.BodyProcessor.fromByteArray (sampleData)) .build ();

5.4. FileProcessor

For at arbejde med en fil kan vi gøre brug af den medfølgende FileProcessor; dens fabriksmetode tager en sti til filen som en parameter og opretter en brødtekst ud fra indholdet:

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

Vi dækkede hvordan man opretter HttpForespørgsel og hvordan man indstiller yderligere parametre i det.

Nu er det tid til at se nærmere på HttpClient klasse, der er ansvarlig for at sende anmodninger og modtage svar.

6. HttpClient

Alle anmodninger sendes vha HttpClient som kan instantieres ved hjælp af HttpClient.newBuilder () metode eller ved at ringe HttpClient.newHttpClient ().

Det giver mange nyttige og selvbeskrivende metoder, vi kan bruge til at håndtere vores anmodning / svar.

Lad os dække nogle af disse her.

6.1. Indstilling af en proxy

Vi kan definere en proxy til forbindelsen. Bare ring bare proxy () metode på en Bygger eksempel:

HttpResponse respons = HttpClient .newBuilder () .proxy (ProxySelector.getDefault ()) .build () .send (anmodning, HttpResponse.BodyHandler.asString ()); 

I vores eksempel brugte vi standardsystemproxyen.

6.2. Indstilling af omdirigeringspolitikken

Nogle gange er den side, vi vil have adgang til, flyttet til en anden adresse.

I så fald modtager vi HTTP-statuskode 3xx, normalt med oplysningerne om ny URI. HttpClient kan omdirigere anmodningen til den nye URI automatisk, hvis vi indstiller den relevante omdirigeringspolitik.

Vi kan gøre det med followRedirects () metode til Bygger:

HttpResponse respons = HttpClient.newBuilder (). FollowRedirects (HttpClient.Redirect.ALWAYS) .build () .send (anmodning, HttpResponse.BodyHandler.asString ());

Alle politikker er defineret og beskrevet i enum HttpClient.Redirect.

6.3. Indstilling Authenticator for en forbindelse

En Authenticator er et objekt, der forhandler legitimationsoplysninger (HTTP-godkendelse) til en forbindelse.

Det giver forskellige godkendelsesordninger (som f.eks. Grundlæggende eller fordøjelsesgodkendelse). I de fleste tilfælde kræver godkendelse brugernavn og adgangskode for at oprette forbindelse til en server.

Vi kan bruge PasswordAuthentication klasse, der kun er indehaver af disse værdier:

HttpResponse respons = HttpClient.newBuilder () .authenticator (ny Authenticator () {@Override beskyttet PasswordAuthentication getPasswordAuthentication () {returner ny PasswordAuthentication ("brugernavn", "password" .toCharArray ());}}). Build () .send (anmodning, HttpResponse.BodyHandler.asString ());

I eksemplet ovenfor passerede vi brugernavnet og adgangskoden som en almindelig tekst; naturligvis i et produktionsscenarie skal dette være anderledes.

Bemærk, at ikke alle anmodninger skal bruge det samme brugernavn og adgangskode. Det Authenticator klasse giver et antal getXXX (f.eks., getRequestingSite ()) metoder, der kan bruges til at finde ud af, hvilke værdier der skal gives.

Nu skal vi udforske en af ​​de mest nyttige funktioner i nye HttpClient - asynkrone opkald til serveren.

6.4. Send anmodninger - Synkroniser vs. Async

Ny HttpClient giver to muligheder for at sende en anmodning til en server:

  • sende(…) - synkront (blokerer indtil svaret kommer)
  • sendAsync (…) - asynkront (venter ikke på svaret, ikke-blokering)

Indtil nu har den sende(...) metode venter naturligvis på et svar:

HttpResponse respons = HttpClient.newBuilder () .build () .send (anmodning, HttpResponse.BodyHandler.asString ()); 

Dette opkald returnerer et HttpResponse objekt, og vi er sikre på, at den næste instruktion fra vores applikationsstrøm kun udføres, når svaret allerede er her.

Det har dog mange ulemper, især når vi behandler store mængder data.

Så nu kan vi bruge sendAsync (...) metode - som vender tilbage FuldførelseFunktionat behandle en anmodning asynkront:

Fuldført respons = HttpClient.newBuilder () .build () .sendAsync (anmodning, HttpResponse.BodyHandler.asString ());

Den nye API kan også håndtere flere svar og streame anmodnings- og svarorganerne:

Liste mål = Arrays.asList (ny URI ("// postman-echo.com/get?foo1=bar1"), ny URI ("// postman-echo.com/get?foo2=bar2")); HttpClient-klient = HttpClient.newHttpClient (); Liste futures = target.stream () .map (target -> client .sendAsync (HttpRequest.newBuilder (target). GET (). build (), HttpResponse.BodyHandler.asString ()). derefter Anvend (svar -> respons.body ( (Collectors.toList ());

6.5. Indstilling Eksekutor til asynkrone opkald

Vi kan også definere en Eksekutor som giver tråde, der skal bruges af asynkrone opkald.

På denne måde kan vi f.eks. Begrænse antallet af tråde, der bruges til behandling af anmodninger:

ExecutorService executorService = Executors.newFixedThreadPool (2); Fuldført respons1 = HttpClient.newBuilder () .executor (executorService) .build () .sendAsync (anmodning, HttpResponse.BodyHandler.asString ()); Fuldført respons2 = HttpClient.newBuilder () .executor (executorService) .build () .sendAsync (anmodning, HttpResponse.BodyHandler.asString ());

Som standard er HttpClient bruger eksekutor java.util.concurrent.Executors.newCachedThreadPool ().

6.6. Definition af en CookieManager

Med ny API og builder er det ligetil at indstille en CookieManager til vores forbindelse. Vi kan bruge builder-metoden cookieManager (CookieManager cookieManager) at definere klientspecifik CookieManager.

Lad os for eksempel definere CookieManager som slet ikke tillader at acceptere cookies:

HttpClient.newBuilder () .cookieManager (ny CookieManager (null, CookiePolicy.ACCEPT_NONE)) .build (); 

Hvis vores CookieManager gør det muligt at gemme cookies, kan vi få adgang til dem ved at kontrollere CookieManager fra vores HttpClient:

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

Lad os nu fokusere på den sidste klasse fra Http API - the HttpResponse.

7. HttpResponse Objekt

Det HttpResponse klasse repræsenterer svaret fra serveren. Det giver en række nyttige metoder - men to af de vigtigste er:

  • statusKode () - returnerer statuskode (type int) for et svar (HttpURLforbindelse klasse indeholder mulige værdier)
  • legeme() - returnerer et organ til et svar (returtype afhænger af svaret BodyHandler parameter videregivet til sende() metode)

Svarobjektet har en anden nyttig metode, som vi dækker som uri (), overskrifter (), trailere () og version().

7.1. URI af svarobjekt

Metoden uri () på svarobjektet returnerer URI hvorfra vi modtog svaret.

Nogle gange kan det være anderledes end URI i anmodningsobjektet, fordi en omdirigering kan forekomme:

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

7.2. Overskrifter fra svar

Vi kan få overskrifter fra svaret ved at ringe til metoden overskrifter () på et svarobjekt:

HttpResponse respons = HttpClient.newHttpClient () .send (anmodning, HttpResponse.BodyHandler.asString ()); HttpHeaders responseHeaders = respons.headers ();

Det vender tilbage HttpHeaders objekt som returtype. Dette er en ny type defineret i jdk.inkubator.http pakke, der repræsenterer en skrivebeskyttet visning af HTTP-headere.

Det har nogle nyttige metoder, der forenkler søgning efter headers værdi.

7.3. Få trailere fra Response

HTTP-svaret kan indeholde yderligere overskrifter, der er inkluderet efter responsindholdet. Disse headere kaldes trailerheadere.

Vi kan få dem ved at ringe til metoden trailere ()HttpRespons:

HttpResponse respons = HttpClient.newHttpClient () .send (anmodning, HttpResponse.BodyHandler.asString ()); CompletableFuture trailere = respons.trailers (); 

Noter det trailere () metoden vender tilbage Fuldført objekt.

7.4. Version af svaret

Metoden version() definerer hvilken version af HTTP-protokol, der blev brugt til at tale med en server.

Husk, at selvom vi definerer, at vi vil bruge HTTP / 2, kan serveren svare via HTTP / 1.1.

Den version, som serveren svarede på, er specificeret i svaret:

HttpRequest anmodning = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get")) .version (HttpClient.Version.HTTP_2) .GET () .build (); HttpResponse respons = HttpClient.newHttpClient () .send (anmodning, HttpResponse.BodyHandler.asString ()); assertThat (respons.version (), equalTo (HttpClient.Version.HTTP_1_1));

8. Java 11 Http-klient

Den største ændring i Java 11 var standardiseringen af HTTP-klient-API, der implementerer HTTP / 2 og Web Socket. Det sigter mod at erstatte arven HttpUrlConnection klasse, som har været til stede i JDK siden Java's meget tidlige år.

Ændringen blev implementeret som en del af JEP 321.

8.1. Større ændringer som en del af JEP 321

  1. Den inkuberede HTTP API fra Java 9 er nu officielt inkorporeret i Java SE API. De nye HTTP API'er kan findes i java.net.HTTP. *
  2. Den nyere version af HTTP-protokollen er designet til at forbedre den samlede ydeevne ved at sende anmodninger fra en klient og modtage svar fra serveren. Dette opnås ved at indføre en række ændringer som streammultiplexing, headerkomprimering og push-løfter.
  3. Fra og med Java 11, API'en er nu fuldt asynkron (den tidligere HTTP / 1.1-implementering blokerede). Asynkrone opkald implementeres ved hjælp af Fuldført.Det Fuldført implementering sørger for at anvende hvert trin, når det forrige er afsluttet, så hele denne strømning er asynkron.
  4. Den nye HTTP-klient-API giver en standard måde at udføre HTTP-netværksoperationer med support til moderne webfunktioner såsom HTTP / 2 uden behov for at tilføje afhængigheder fra tredjepart.
  5. De nye API'er giver indbygget support til HTTP 1.1 / 2 WebSocket. Kerneklasser og interface, der giver kernefunktionaliteten, inkluderer:
  • Det HttpClient-klasse, java.net.http.HttpClient
  • Det HttpForespørgsel klasse, java.net.http.HttpRequest
  • Det HttpResponse interface, java.net.http.HttpResponse
  • Det WebSocket interface, java.net.http.WebSocket

8.2. Problemer med Pre Java 11 HTTP-klienten

Det eksisterende HttpURLforbindelse API og dets implementering havde adskillige problemer:

  • URLConnection API blev designet med flere protokoller, der nu ikke længere fungerer (FTP, gopher osv.).
  • API'en går forud for HTTP / 1.1 og er for abstrakt.
  • Det fungerer kun i blokeringstilstand (dvs. en tråd pr. Anmodning / svar).
  • Det er meget svært at vedligeholde.

9. Ændringer i Http-klient med Java 11

9.1. Introduktion af statiske fabriksklasser

Nye statiske fabriksklasser BodyPublishers, BodySubscribers, og BodyHandlers introduceres, der inkluderer eksisterende implementeringer af BodyPublisher, BodySubscriber og BodyHandler.

Disse bruges til at udføre nyttige almindelige opgaver, såsom at håndtere svarteksten som en streng eller streame kroppen til en fil.

For f.eks. i Pre Java 11 måtte vi gøre noget som dette:

HttpResponse respons = client.send (anmodning, HttpResponse.BodyHandler.asString ());

Som vi nu kan forenkle som:

HttpResponse respons = client.send (anmodning, BodyHandlers.ofString ());

Navnet på statiske metoder er også standardiseret for mere klarhed.

For f.eks. metoder navne som fraXxx bruges, når vi bruger dem som adaptere eller navne som afXxx når vi opretter foruddefinerede håndterere / abonnenter.

9.2. Flydende metoder til almindelige kropstyper

Praktiske fabriksmetoder til oprettede forlag og håndtere til håndtering af almindelige kropstyper er blevet introduceret.

For f.eks. Vi har nedenstående flydende metoder til at oprette udgivere fra bytes, filer og strenge:

BodyPublishers.ofByteArray BodyPublishers.ofFile BodyPublishers.ofString

På samme måde kan vi bruge til at oprette håndterere fra disse almindelige kropstyper:

BodyHandlers.ofByteArray BodyHandlers.ofString BodyHandlers.ofFile

9.3. Andre API-ændringer

1. Med denne nye API bruger vi BodyHandlers.discarding () og BodyHandlers.replacing (værdi) i stedet for kassér (udskiftning af objekt):

HttpResponse respons1 = HttpClient.newHttpClient () .send (anmodning, BodyHandlers.discarding ());
HttpResponse respons1 = HttpClient.newHttpClient () .send (anmodning, BodyHandlers.replacing (værdi));

2. Ny metode ofLines () i BodyHandlers tilføjes for at håndtere streaming af responsorganet som en Stream of lines.

3. fraLineSubscriber metoden tilføjes i BodyHandlers klasse, der kan bruges som adapter mellem en BodySubscriber og en tekstbaseret Flow. Abonnent der analyserer tekst linje for linje.

4. Tilføjet en ny BodySubscriber.mapping i BodySubscribers klasse, der kan bruges til kortlægning fra en responskropstype til en anden ved at anvende den givne funktion på kropsobjektet.

5. I HttpClient.Redirect, enum-konstanter SAME_PROTOCOL og SIKKER politik erstattes med et nyt rum NORMAL.

10. Håndtering af push-løfter i HTTP / 2

Ny Http-klient understøtter push-løfter igennem PushPromiseHandler interface.

Det giver serveren mulighed for at "skubbe" indhold til klienten yderligere ressourcer, mens han anmoder om den primære ressource, sparer mere returflyvning og forbedrer som resultat ydeevnen i sidegengivelse.

Det er virkelig multiplexeringsfunktionen i HTTP / 2, der giver os mulighed for at glemme ressourcebundning. For hver ressource sender serveren en særlig anmodning, kendt som et push-løfte til klienten.

Push-løfter modtaget, hvis nogen, håndteres af det givne PushPromiseHandler. En nulvurderet PushPromiseHnadler afviser ethvert push-løfte.

Det HttpClient har overbelastet sendAsync metode, der giver os mulighed for at håndtere sådanne løfter, som vist i nedenstående eksempel.

Lad os først oprette en PushPromiseHandler:

privat statisk PushPromiseHandler pushPromiseHandler () {return (HttpRequest initiatingRequest, HttpRequest pushPromiseRequest, Function> acceptor) -> {acceptor.apply (BodyHandlers.ofString ()) .thenAccept (resp -> {System.out.println ("Pushed response:" + resp.uri () + ", headers:" + resp. headers ());}); System.out.println ("Forespørgsel om løfte:" + pushPromiseRequest.uri ()); System.out.println ("Forespørgsel om løfte:" + pushPromiseRequest.headers ()); }; }

Lad os derefter bruge sendAsync metode til at håndtere dette push-løfte:

httpClient.sendAsync (pageRequest, BodyHandlers.ofString (), pushPromiseHandler ()) .thenAccept (pageResponse -> {System.out.println ("Sidesvar statuskode:" + pageResponse.statusCode ()); System.out.println ( "Sideresponsoverskrifter:" + pageResponse.headers ()); String responseBody = pageResponse.body (); System.out.println (responseBody);}) .join (); 

11. Konklusion

I denne artikel udforskede vi Java 9'er HttpClient API, der giver en masse fleksibilitet og kraftfulde funktioner. Den komplette kode, der bruges til Java 9s HttpClient API, er tilgængelig på GitHub.

Vi udforskede også den nye ændring i Java 11 HttpClient, der standardiserede den inkuberende HttpClient, der blev introduceret i Java 9 med mere kraftfulde ændringer. Kodestykker, der bruges til Java 11 Http Client, er også tilgængelige via Github.

Bemærk: I eksemplerne har vi brugt eksempler på REST-slutpunkter leveret af // postman-echo.com.