Utforske den nye HTTP-klienten i Java

1. Introduksjon

I denne opplæringen vil vi utforske Java 9s nye inkubering HttpClient.

Inntil veldig nylig ga Java bare HttpURLConnection API - som er lavt og ikke er kjent for å være funksjonsriktogBrukervennlig.

Derfor ble noen mye brukte tredjepartsbiblioteker ofte brukt - som Apache HttpClient, Jetty og Spring's RestTemplate.

2. Første oppsett

HTTP-klientmodulen er samlet som en inkubatormodul i JDK 9 og støtter HTTP / 2 med bakoverkompatibilitet som fortsatt letter HTTP / 1.1.

For å bruke den, må vi definere modulen vår ved hjelp av a module-info.java fil som også indikerer den nødvendige modulen for å kjøre applikasjonen vår:

modul com.baeldung.java9.httpclient {krever jdk.incubator.httpclient; }

3. HTTP Client API Oversikt

I motsetning til HttpURLConnection, HTTP Client gir synkron og asynkron forespørselsmekanismer.

API-en består av 3 kjerneklasser:

  • HttpForespørselrepresenterer forespørselen om å bli sendt via HttpClient
  • HttpClientoppfører seg som en container for konfigurasjonsinformasjon som er felles for flere forespørsler
  • HttpResponserepresenterer resultatet av en HttpForespørsel anrop

Vi vil undersøke hver av dem mer detaljert i de følgende avsnittene. La oss først fokusere på en forespørsel.

4. HttpForespørsel

HttpForespørsel, som navnet foreslår, er et objekt som representerer forespørsel vi vil sende. Nye forekomster kan opprettes ved hjelp av HttpRequest.Builder.

Vi kan få det ved å ringe HttpRequest.newBuilder (). Bygger klasse gir en rekke metoder som vi kan bruke til å konfigurere forespørselen vår.

Vi vil dekke de viktigste.

4.1. Omgivelser URI

Det første vi må gjøre når vi oppretter en forespørsel, er å oppgi URL-adressen.

Vi kan gjøre det på to måter - ved å bruke konstruktøren til Bygger med URI parameter eller ved anropsmetode uri (URI)Bygger forekomst:

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

Det siste vi må konfigurere for å lage en grunnleggende forespørsel er en HTTP-metode.

4.2. Spesifisere HTTP-metoden

Vi kan definere HTTP-metoden som forespørselen vår vil bruke ved å ringe en av metodene fra Bygger:

  • FÅ()
  • POST (BodyProcessor body)
  • PUT (BodyProcessor body)
  • DELETE (BodyProcessor body)

Vi skal dekke BodyProcessor i detalj, senere. La oss bare lage et veldig enkelt eksempel på GET-forespørsel:

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

Denne forespørselen har alle parametere som kreves av HttpClient. Noen ganger må vi imidlertid legge til flere parametere i forespørselen vår; her er noen viktige er:

  • versjonen av HTTP-protokollen
  • topptekster
  • en timeout

4.3. Angir versjon av HTTP-protokoll

API bruker fullt ut HTTP / 2-protokollen og bruker den som standard, men vi kan definere hvilken versjon av protokollen vi vil bruke.

HttpRequest-forespørsel = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get")) .versjon (HttpClient.Version.HTTP_2) .GET () .build ();

Viktig å nevne her er at klienten faller tilbake til for eksempel HTTP / 1.1 hvis HTTP / 2 ikke støttes.

4.4. Sette overskrifter

Hvis vi vil legge til flere overskrifter i forespørselen vår, kan vi bruke de angitte byggemetodene.

Vi kan gjøre det på en av to måter:

  • overføring av alle overskrifter som nøkkelverdipar til topptekster () metode eller ved
  • ved hjelp av Overskrift() metode for enkelt nøkkelverdioverskrift:
HttpRequest-forespørsel = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get")). Headers ("key1", "value1", "key2", "value2") .GET () .bygge(); HttpRequest request2 = HttpRequest.newBuilder () .uri (ny URI ("// postman-echo.com/get")). Header ("key1", "value1"). Header ("key2", "value2"). GET () .build (); 

Den siste nyttige metoden vi kan bruke til å tilpasse forespørselen vår er en timeout ().

4.5. Angi en tidsavbrudd

La oss nå definere hvor lang tid vi vil vente på svar.

Hvis den angitte tiden utløper, a HttpTimeoutException vil bli kastet; standard tidsavbrudd er satt til uendelig.

Tidsavbruddet kan stilles inn med Varighet objekt - ved å ringe metoden pause() på byggherreinstansen:

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

5. Sette et anmodningsorgan

Vi kan legge til en kropp i en forespørsel ved å bruke metodene for forespørselbygger: POST (BodyProcessor body), PUT (BodyProcessor body) og SLETT (BodyProcessor body).

Den nye API-en gir en rekke BodyProcessor implementeringer utenom boksen som forenkler overføring av forespørselsorganet:

  • StringProsessor (leser kropp fra en String, opprettet med HttpRequest.BodyProcessor.fromString)
  • InputStreamProcessor (leser kropp fra en InputStream, opprettet med HttpRequest.BodyProcessor.fromInputStream)
  • ByteArrayProcessor (leser brødtekst fra et byte-utvalg, opprettet med HttpRequest.BodyProcessor.fromByteArray)
  • FileProcessor (leser brødtekst fra en fil på den angitte banen, opprettet med HttpRequest.BodyProcessor.fromFile)

I tilfelle vi ikke trenger et legeme, kan vi bare passere i et HttpRequest.noBody ():

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

5.1. StringBodyProcessor

Sette et forespørselsorgan med hvilken som helst BodyProcessor implementering er veldig enkel og intuitiv.

For eksempel hvis vi ønsker å gi et enkelt String som kropp kan vi bruke StringBodyProcessor.

Som vi allerede nevnte, kan dette objektet opprettes med en fabrikkmetode fromString (); det tar bare en String objekt som et argument og skaper en kropp ut fra det:

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

5.2. InputStreamBodyProcessor

For å gjøre det, InputStream må bestås som en Leverandør (for å gjøre skapelsen lat), så den er litt annerledes enn beskrevet ovenfor StringBodyProcessor.

Dette er imidlertid også ganske greit:

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

Legg merke til hvordan vi brukte en enkel ByteArrayInputStream her; det kan selvfølgelig være noe InputStream gjennomføring.

5.3. ByteArrayProcessor

Vi kan også bruke ByteArrayProcessor og send en rekke byte som parameter:

byte [] sampleData = "Eksempel på forespørselens brødtekst" .getBytes (); HttpRequest-forespørsel = 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 å jobbe med en fil, kan vi bruke den medfølgende FileProcessor; fabrikkmetoden tar en sti til filen som en parameter og oppretter en kropp fra innholdet:

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

Vi dekket hvordan du lager HttpForespørsel og hvordan du setter ytterligere parametere i den.

Nå er det på tide å se nærmere på HttpClient klasse som er ansvarlig for å sende forespørsler og motta svar.

6. HttpClient

Alle forespørsler sendes vha HttpClient som kan instantieres ved hjelp av HttpClient.newBuilder () metode eller ved å ringe HttpClient.newHttpClient ().

Det gir mange nyttige og selvbeskrivende metoder vi kan bruke til å håndtere vår forespørsel / respons.

La oss dekke noen av disse her.

6.1. Sette en proxy

Vi kan definere en fullmektig for tilkoblingen. Bare ring proxy () metode på en Bygger forekomst:

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

I vårt eksempel brukte vi standard systemproxy.

6.2. Angi viderekoblingspolitikken

Noen ganger har siden vi vil ha tilgang til, flyttet til en annen adresse.

I så fall vil vi motta HTTP-statuskode 3xx, vanligvis med informasjon om ny URI. HttpClient kan omdirigere forespørselen til den nye URI automatisk hvis vi setter den riktige viderekoblingspolitikken.

Vi kan gjøre det med followRedirects () metode på Bygger:

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

All policy er definert og beskrevet i enum HttpClient.Redirect.

6.3. Omgivelser Authenticator for en tilkobling

An Authenticator er et objekt som forhandler legitimasjon (HTTP-godkjenning) for en tilkobling.

Det gir forskjellige autentiseringsplaner (som f.eks. Grunnleggende eller fordøyelsesautentisering). I de fleste tilfeller krever godkjenning brukernavn og passord for å koble til en server.

Vi kan bruke PasswordAuthentication klasse som bare er innehaver av disse verdiene:

HttpResponse response = HttpClient.newBuilder () .authenticator (new Authenticator () {@Override protected PasswordAuthentication getPasswordAuthentication () {return new PasswordAuthentication ("brukernavn", "passord" .toCharArray ());}}). Build () .send (forespørsel, HttpResponse.BodyHandler.asString ());

I eksemplet ovenfor passerte vi brukernavn og passordverdier som ren tekst; selvfølgelig, i et produksjonsscenario, må dette være annerledes.

Merk at ikke alle forespørsler skal bruke samme brukernavn og passord. De Authenticator klasse gir et antall getXXX (f.eks. getRequestingSite ()) metoder som kan brukes til å finne ut hvilke verdier som skal oppgis.

Nå skal vi utforske en av de mest nyttige funksjonene i nye HttpClient - asynkrone anrop til serveren.

6.4. Send forespørsler - Synkroniser kontra Async

Nye HttpClient gir to muligheter for å sende en forespørsel til en server:

  • sende(…) - synkront (blokkerer til svaret kommer)
  • sendAsync (…) - asynkront (venter ikke på svaret, ikke-blokkerende)

Inntil nå har sende(...) metoden venter naturlig nok på et svar:

HttpResponse respons = HttpClient.newBuilder () .build () .send (forespørsel, HttpResponse.BodyHandler.asString ()); 

Denne samtalen returnerer en HttpResponse objektet, og vi er sikre på at neste instruksjon fra applikasjonsflyten vår bare blir utført når svaret allerede er her.

Imidlertid har det mange ulemper, spesielt når vi behandler store datamengder.

Så nå kan vi bruke sendAsync (...) metode - som returnerer Fullførbar funksjonå behandle en forespørsel asynkront:

Fullførbar fremtid respons = HttpClient.newBuilder () .build () .sendAsync (forespørsel, HttpResponse.BodyHandler.asString ());

Den nye API-en kan også håndtere flere svar, og streame forespørselen og svarorganene:

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 ()) .thenApply (respons -> respons.body ( (Collectors.toList ());

6.5. Omgivelser Leder for asynkrone samtaler

Vi kan også definere en Leder som gir tråder som skal brukes av asynkrone samtaler.

På denne måten kan vi for eksempel begrense antall tråder som brukes til å behandle forespørsler:

ExecutorService executorService = Executors.newFixedThreadPool (2); Fullførbar fremtid respons1 = HttpClient.newBuilder () .executor (executorService) .build () .sendAsync (forespørsel, HttpResponse.BodyHandler.asString ()); Fullførbar fremtid respons2 = HttpClient.newBuilder () .executor (executorService) .build () .sendAsync (forespørsel, HttpResponse.BodyHandler.asString ());

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

6.6. Definere en CookieManager

Med ny API og builder er det greit å sette en CookieManager for forbindelsen vår. Vi kan bruke byggemetoden cookieManager (CookieManager cookieManager) for å definere klientspesifikk CookieManager.

La oss for eksempel definere CookieManager som ikke tillater å akseptere informasjonskapsler i det hele tatt:

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

I tilfelle vår CookieManager lar informasjonskapsler lagres, vi kan få tilgang til dem ved å sjekke CookieManager fra vår HttpClient:

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

La oss nå fokusere på den siste klassen fra Http API - the HttpResponse.

7. HttpResponse Gjenstand

De HttpResponse klasse representerer svaret fra serveren. Det gir en rekke nyttige metoder - men to av de viktigste er:

  • statusKode () - returnerer statuskode (type int) for svar (HttpURLConnection klasse inneholder mulige verdier)
  • kropp() - returnerer et organ for et svar (returtype avhenger av responsen BodyHandler parameteren sendt til sende() metode)

Svarobjektet har en annen nyttig metode som vi vil dekke som uri (), topptekster (), tilhengere () og versjon().

7.1. URI av responsobjekt

Metoden uri () på svarobjektet returnerer URI som vi fikk svaret fra.

Noen ganger kan det være annerledes enn URI i forespørselsobjektet, fordi en omdirigering kan forekomme:

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

7.2. Overskrifter fra Respons

Vi kan skaffe overskrifter fra svaret ved å ringe metoden topptekster () på et svarobjekt:

HttpResponse respons = HttpClient.newHttpClient () .send (forespørsel, HttpResponse.BodyHandler.asString ()); HttpHeaders responseHeaders = response.headers ();

Det kommer tilbake HttpHeaders objekt som returtype. Dette er en ny type definert i jdk.inkubator.http pakke som representerer en skrivebeskyttet visning av HTTP-hoder.

Den har noen nyttige metoder som forenkler søket etter overskriftsverdi.

7.3. Få trailere fra Response

HTTP-responsen kan inneholde flere overskrifter som er inkludert etter svarinnholdet. Disse topptekstene kalles trailerhoder.

Vi kan skaffe dem ved å ringe metoden tilhengere ()HttpRespons:

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

Noter det tilhengere () metoden returnerer Fullførbar fremtid gjenstand.

7.4. Versjon av svaret

Metoden versjon() definerer hvilken versjon av HTTP-protokollen som ble brukt til å snakke med en server.

Husk at selv om vi definerer at vi vil bruke HTTP / 2, kan serveren svare via HTTP / 1.1.

Versjonen som serveren svarte på, er spesifisert i svaret:

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

8. Java 11 Http-klient

Den største endringen i Java 11 var standardiseringen av HTTP-klient-API som implementerer HTTP / 2 og Web Socket. Det tar sikte på å erstatte arven HttpUrlConnection klasse som har vært til stede i JDK siden de første årene av Java.

Endringen ble implementert som en del av JEP 321.

8.1. Store endringer som en del av JEP 321

  1. Den inkuberte HTTP API fra Java 9 er nå offisielt innlemmet i Java SE API. De nye HTTP-API-ene finner du i java.net.HTTP. *
  2. Den nyere versjonen av HTTP-protokollen er designet for å forbedre den generelle ytelsen for å sende forespørsler fra en klient og motta svar fra serveren. Dette oppnås ved å innføre en rekke endringer som strømmultipleksering, toppkomprimering og push-løfter.
  3. Fra og med Java 11, API-en er nå fullstendig asynkron (den forrige HTTP / 1.1-implementeringen blokkerte). Asynkrone anrop implementeres ved hjelp av Fullførbar fremtid.De Fullførbar fremtid implementeringen tar seg av å bruke hvert trinn når den forrige er ferdig, så hele strømmen er asynkron.
  4. Den nye API-en for HTTP-klient gir en standard måte å utføre HTTP-nettverksoperasjoner med støtte for moderne webfunksjoner som HTTP / 2, uten å måtte legge til tredjepartsavhengigheter.
  5. De nye API-ene gir innebygd støtte for HTTP 1.1 / 2 WebSocket. Kjerneklassene og grensesnittet som gir kjernefunksjonaliteten inkluderer:
  • De HttpClient-klasse, java.net.http.HttpClient
  • De HttpForespørsel klasse, java.net.http.HttpRequest
  • De HttpResponse grensesnitt, java.net.http.HttpResponse
  • De WebSocket grensesnitt, java.net.http.WebSocket

8.2. Problemer med Pre Java 11 HTTP-klient

Den eksisterende HttpURLConnection API og implementeringen hadde mange problemer:

  • URLConnection API ble designet med flere protokoller som nå ikke lenger fungerer (FTP, gopher, etc.).
  • API-en går forut for HTTP / 1.1 og er for abstrakt.
  • Det fungerer bare i blokkeringsmodus (dvs. en tråd per forespørsel / svar).
  • Det er veldig vanskelig å vedlikeholde.

9. Endringer i Http Client med Java 11

9.1. Innføring av statiske fabrikklasser

Nye statiske fabrikklasser BodyPublishers, Kroppsabonnenter, og BodyHandlers introduseres som inkluderer eksisterende implementeringer av BodyPublisher, BodySubscriber og BodyHandler.

Disse brukes til å utføre nyttige vanlige oppgaver, for eksempel å håndtere respons kroppen som en streng eller streame kroppen til en fil.

For f.eks. i Pre Java 11 måtte vi gjøre noe slikt:

HttpResponse respons = client.send (forespørsel, HttpResponse.BodyHandler.asString ());

Som vi nå kan forenkle som:

HttpResponse respons = client.send (forespørsel, BodyHandlers.ofString ());

Også navnet på statiske metoder er standardisert for mer klarhet.

For f.eks. metoder navn som fraXxx brukes når vi bruker dem som adaptere eller navn som ofXxx når vi lager forhåndsdefinerte håndterere / abonnenter.

9.2. Flytende metoder for vanlige kroppstyper

Praktiske fabrikkmetoder for skapte forlag og håndtere for håndtering av vanlige kroppstyper er introdusert.

For f.eks. Vi har nedenfor flytende metoder for å lage utgivere fra byte, filer og strenger:

BodyPublishers.ofByteArray BodyPublishers.ofFile BodyPublishers.ofString

På samme måte kan vi bruke for å lage håndterere fra disse vanlige kroppstyper:

BodyHandlers.ofByteArray BodyHandlers.ofString BodyHandlers.ofFile

9.3. Andre API-endringer

1. Med denne nye API-en vil vi bruke BodyHandlers. Kassering () og BodyHandlers.replacing (verdi) i stedet for kast (gjenstandsbytte):

HttpResponse respons1 = HttpClient.newHttpClient () .send (forespørsel, BodyHandlers.discarding ());
HttpResponse respons1 = HttpClient.newHttpClient () .send (forespørsel, BodyHandlers.replacing (verdi));

2. Ny metode ofLines () i BodyHandlers blir lagt til for å håndtere strømming av responsorganet som en strøm av linjer.

3. fraLineSubscriber metoden er lagt til BodyHandlers klasse som kan brukes som adapter mellom en BodySubscriber og en tekstbasert Flow. Abonnent som analyserer tekst linje for linje.

4. Lagt til en ny BodySubscriber.mapping i BodySubscribers klasse som kan brukes til å kartlegge fra en respons kroppstype til en annen ved å bruke den gitte funksjonen på kroppsobjektet.

5. I HttpClient.Redirect, enum konstanter SAME_PROTOCOL og SIKRE politikken erstattes med et nytt enum VANLIG.

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

Ny Http-klient støtter push-løfter gjennom PushPromiseHandler grensesnitt.

Det gjør det mulig for serveren å "skyve" innhold til klienten ytterligere ressurser mens de ber om den primære ressursen, og sparer mer tur-retur og som et resultat forbedrer ytelsen i gjengivelse av sider.

Det er virkelig multiplexing-funksjonen til HTTP / 2 som lar oss glemme ressurssamling. For hver ressurs sender serveren en spesiell forespørsel, kjent som et push-løfte til klienten.

Push-løfter mottatt, hvis noen, håndteres av det gitte PushPromiseHandler. En null verdsatt PushPromiseHnadler avviser push-løfter.

De HttpClient har en overbelastet sendAsync metode som lar oss håndtere slike løfter, som vist i eksemplet nedenfor.

La oss først lage en PushPromiseHandler:

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

Neste, la oss bruke sendAsync metode for å håndtere dette push-løftet:

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

11. Konklusjon

I denne artikkelen utforsket vi Java 9-er HttpClient API som gir mye fleksibilitet og kraftige funksjoner. Den komplette koden som brukes til Java 9s HttpClient API, er tilgjengelig på GitHub.

Vi utforsket også den nye endringen i Java 11 HttpClient, som standardiserte den inkuberende HttpClient introdusert i Java 9 med kraftigere endringer. Kodebitene som brukes til Java 11 Http Client, er også tilgjengelig via Github.

Merk: I eksemplene har vi brukt eksempler på REST-endepunkter levert av //postman-echo.com.