En guide til OkHttp

1. Introduksjon

I denne artikkelen viser vi det grunnleggende om å sende forskjellige typer HTTP-forespørsler, motta og tolke HTTP-svar, og hvordan du konfigurerer en klient med OkHttp.

Vi vil også gå inn i mer avanserte bruksområder for å konfigurere en klient med tilpassede overskrifter, tidsavbrudd, hurtigbufring osv.

2. OkHttp Oversikt

OkHttp er en effektiv HTTP & HTTP / 2-klient for Android- og Java-applikasjoner.

Den kommer med avanserte funksjoner som tilkoblingssamling (hvis HTTP / 2 ikke er tilgjengelig), gjennomsiktig GZIP-komprimering og responsbufring for å unngå nettverket helt for gjentatte forespørsler.

Det er også i stand til å gjenopprette fra vanlige tilkoblingsproblemer, og hvis en tjeneste har flere IP-adresser, kan den på nytt prøve forespørselen om alternative adresser.

På et høyt nivå er klienten designet for både blokkering av synkrone og ikke-blokkerende asynkrone samtaler.

OkHttp støtter Android 2.3 og nyere. For Java er minimumskravet 1,7.

Etter denne korte oversikten, la oss se noen brukseksempler.

3. Maven avhengighet

La oss først legge til biblioteket som en avhengighet av pom.xml:

 com.squareup.okhttp3 okhttp 3.4.2 

For å se den siste avhengigheten av dette biblioteket, sjekk ut siden på Maven Central.

4. Synkron FÅ MED OkHttp

For å sende en synkron GET-forespørsel må vi bygge en Be om objekt basert på en URL og lag en Anrop. Etter utførelsen får vi tilbake en forekomst av Respons:

@Test offentlig ugyldig nårGetRequest_thenCorrect () kaster IOException {Request request = new Request.Builder () .url (BASE_URL + "/ date") .build (); Call call = client.newCall (forespørsel); Svarrespons = call.execute (); assertThat (respons.code (), equalTo (200)); }

5. Asynkron FÅ MED OkHttp

Nå, for å lage en asynkron GET, må vi legge inn en Anrop. EN Ring tilbake lar oss lese svaret når det er lesbart. Dette skjer etter at svaroverskriftene er klare.

Lesing av responsorganet kan fortsatt blokkere. OkHttp tilbyr for øyeblikket ingen asynkrone APIer for å motta et svarorgan i deler:

@Test offentlig ugyldig nårAsynchronousGetRequest_thenCorrect () {Request request = new Request.Builder () .url (BASE_URL + "/ date") .build (); Call call = client.newCall (forespørsel); call.enqueue (new Callback () {public void onResponse (Call call, Response response) kaster IOException {// ...} public void onFailure (Call call, IOException e) {fail ();}}); }

6. GET With Query Parameters

Til slutt, for å legge til søkeparametere i vår GET-forespørsel, kan vi dra nytte av HttpUrl.Builder.

Etter at URL-en er bygget kan vi sende den til vår Be om gjenstand:

@Test offentlig ugyldig nårGetRequestWithQueryParameter_thenCorrect () kaster IOException {HttpUrl.Builder urlBuilder = HttpUrl.parse (BASE_URL + "/ ex / bars"). NewBuilder (); urlBuilder.addQueryParameter ("id", "1"); String url = urlBuilder.build (). ToString (); Be om forespørsel = ny Request.Builder () .url (url) .build (); Call call = client.newCall (forespørsel); Svarrespons = call.execute (); assertThat (respons.code (), equalTo (200)); }

7. POST-forespørsel

La oss se på en enkel POST-forespørsel der vi bygger en RequestBody for å sende parametrene “Brukernavn” og "passord":

@Test offentlig ugyldig nårSendPostRequest_thenCorrect () kaster IOException {RequestBody formBody = ny FormBody.Builder () .add ("brukernavn", "test") .add ("passord", "test") .build (); Be om forespørsel = ny Request.Builder () .url (BASE_URL + "/ brukere") .post (formBody) .build (); Call call = client.newCall (forespørsel); Svarrespons = call.execute (); assertThat (respons.code (), equalTo (200)); }

Vår artikkel A Quick Guide to Post Requests with OkHttp har flere eksempler på POST-forespørsler med OkHttp.

8. Filopplasting

8.1. Last opp en fil

I dette eksemplet ser vi hvordan du laster opp en Fil. Vi laster opp "test.ext ” fil ved hjelp av MultipartBody.Builder:

@Test offentlig ugyldig nårUploadFile_thenCorrect () kaster IOException {RequestBody requestBody = ny MultipartBody.Builder () .setType (MultipartBody.FORM) .addFormDataPart ("fil", "file.txt", RequestBody.create (MediaType.parse ("applikasjon /" applikasjon / "applikasjon /" oktett-stream "), ny fil (" src / test / resources / test.txt "))) .build (); Be om forespørsel = ny Request.Builder () .url (BASE_URL + "/ brukere / opplasting") .post (requestBody) .build (); Call call = client.newCall (forespørsel); Svarrespons = call.execute (); assertThat (respons.code (), equalTo (200)); }

8.2. Få fremgang for filopplasting

Til slutt, la oss se hvordan du får fremdriften til a Fil laste opp. Vi vil utvide RequestBody for å få synlighet i opplastingsprosessen.

Først, her er opplastingsmetoden:

@Test offentlig ugyldig nårGetUploadFileProgress_thenCorrect () kaster IOException {RequestBody requestBody = ny MultipartBody.Builder () .setType (MultipartBody.FORM) .addFormDataPart ("fil", "file.txt", RequestBody.create (MediaType.parse) octet-stream "), ny fil (" src / test / resources / test.txt "))) .build (); ProgressRequestWrapper.ProgressListener lytter = (bytesWritten, contentLength) -> {float prosent = 100f * bytesWritten / contentLength; assertFalse (Float.compare (prosent, 100)> 0); }; ProgressRequestWrapper countingBody = ny ProgressRequestWrapper (requestBody, lytter); Be om forespørsel = ny Request.Builder () .url (BASE_URL + "/ brukere / opplasting") .post (countingBody) .build (); Call call = client.newCall (forespørsel); Svarrespons = call.execute (); assertThat (respons.code (), equalTo (200)); } 

Her er grensesnittet ProgressListener som gjør det mulig å observere fremdriften for opplastingen:

offentlig grensesnitt ProgressListener {ugyldig påRequestProgress (lange byteSkrevet, langt innholdLengde); }

Her er ProgressRequestWrapper som er den utvidede versjonen av RequestBody:

offentlig klasse ProgressRequestWrapper utvider RequestBody {@Override public void writeTo (BufferedSink sink) kaster IOException {BufferedSink bufferedSink; countingSink = ny CountingSink (vask); bufferedSink = Okio.buffer (countingSink); delegat.writeTo (bufferedSink); bufferedSink.flush (); }}

Til slutt, her er CountingSink som er den utvidede versjonen av VideresendingSynke :

beskyttet klasse CountingSink utvider ForwardingSink {private long bytesWritten = 0; public CountingSink (Sink delegate) {super (delegate); } @Override public void write (Buffer source, long byteCount) kaster IOException {super.write (kilde, byteCount); bytesWritten + = byteCount; listener.onRequestProgress (bytesWritten, contentLength ()); }}

Noter det:

  • Ved utvidelse VideresendingSink til “CountingSink”, vi overstyrer skrivemetoden () for å telle de skrevne (overførte) byte
  • Ved utvidelse RequestBody til "ProgressRequestWrapper “, Vi overstyrer writeTo () -metoden for å bruke vår “ForwardingSink”

9. Angi en tilpasset topptekst

9.1. Sette en overskrift på en forespørsel

For å sette en hvilken som helst tilpasset overskrift på en Be om vi kan bruke en enkel addHeader anrop:

@Test offentlig ugyldig når SetHeader_thenCorrect () kaster IOException {Request request = new Request.Builder () .url (SAMPLE_URL) .addHeader ("Content-Type", "application / json") .build (); Call call = client.newCall (forespørsel); Svarrespons = call.execute (); respons.lukk (); }

9.2. Angi en standard topptekst

I dette eksemplet vil vi se hvordan du konfigurerer en standard topptekst på selve klienten, i stedet for å sette den på hver forespørsel.

For eksempel hvis vi vil angi en innholdstype “Applikasjon / json” for hver forespørsel trenger vi å sette en avlytter for vår klient. Her er metoden:

@Test offentlig ugyldig når SetDefaultHeader_thenCorrect () kaster IOException {OkHttpClient-klient = ny OkHttpClient.Builder () .addInterceptor (ny DefaultContentTypeInterceptor ("applikasjon / json")) .build (); Be om forespørsel = ny Request.Builder () .url (SAMPLE_URL) .build (); Call call = client.newCall (forespørsel); Svarrespons = call.execute (); respons.lukk (); }

Og her er DefaultContentTypeInterceptor som er den utvidede versjonen av Interceptor:

offentlig klasse DefaultContentTypeInterceptor implementerer Interceptor {public Response intercept (Interceptor.Chain chain) kaster IOException {Request originalRequest = chain.request (); Be forespørselWithUserAgent = originalRequest .newBuilder () .header ("Content-Type", contentType) .build (); return chain.proceed (requestWithUserAgent); }}

Merk at avlyssneren legger til overskriften i den opprinnelige forespørselen.

10. Ikke følg viderekoblinger

I dette eksemplet ser vi hvordan du konfigurerer OkHttpClient for å slutte å følge viderekoblinger.

Som standard, hvis en GET-forespørsel blir besvart med en HTTP 301 flyttet permanent viderekoblingen følges automatisk. I noen brukstilfeller kan det være helt greit, men det er absolutt brukstilfeller der det ikke er ønskelig.

For å oppnå denne atferden, når vi bygger vår klient, må vi stille followRedirects til falsk.

Merk at svaret vil returnere et HTTP 301 statuskode:

@Test offentlig ugyldig nårSetFollowRedirects_thenNotRedirected () kaster IOException {OkHttpClient-klient = ny OkHttpClient (). NewBuilder () .followRedirects (false) .build (); Be om forespørsel = ny Request.Builder () .url ("// t.co/I5YYd9tddw") .build (); Call call = client.newCall (forespørsel); Svarrespons = call.execute (); assertThat (respons.code (), equalTo (301)); } 

Hvis vi slår på viderekoblingen med en ekte parameter (eller fjerne den helt), vil klienten følge omdirigering og testen mislykkes da returkoden vil være en HTTP 200.

11. Tidsavbrudd

Bruk tidsavbrudd for å mislykkes i en samtale når kollegaen ikke kan nås. Nettverksfeil kan skyldes problemer med klienttilkobling, problemer med servertilgjengelighet eller noe mellom. OkHttp støtter tidsavbrudd for tilkobling, lesing og skriving.

I dette eksemplet bygde vi vår klient med en readTimeout på 1 sekund, mens URL-en serveres med 2 sekunders forsinkelse:

@Test offentlig ugyldig når SetRequestTimeout_thenFail () kaster IOException {OkHttpClient-klient = ny OkHttpClient.Builder () .readTimeout (1, TimeUnit.SECONDS) .build (); Forespørsel forespørsel = ny Request.Builder () .url (BASE_URL + "/ forsinkelse / 2") .build (); Call call = client.newCall (forespørsel); Svarrespons = call.execute (); assertThat (respons.code (), equalTo (200)); }

Merk at testen mislykkes ettersom klientens tidsavbrudd er lavere enn responstiden for ressursen.

12. Avbryte en samtale

Bruk Call.cancel () for å stoppe en pågående samtale umiddelbart. Hvis en tråd for øyeblikket skriver en forespørsel eller leser et svar, vil en IO Unntak vil bli kastet.

Bruk dette for å bevare nettverket når en samtale ikke lenger er nødvendig; for eksempel når brukeren navigerer vekk fra et program:

@Test (forventet = IOException.class) offentlig ugyldig nårCancelRequest_thenCorrect () kaster IOException {ScheduledExecutorService executor = Executors.newScheduledThreadPool (1); Forespørsel forespørsel = ny Request.Builder () .url (BASE_URL + "/ forsinkelse / 2") .build (); int sekunder = 1; lang startNanos = System.nanoTime (); Call call = client.newCall (forespørsel); executor.schedule (() -> {logger.debug ("Avbryter samtale:" + (System.nanoTime () - startNanos) / 1e9f); call.cancel (); logger.debug ("Avlyst samtale:" + (System .nanoTime () - startNanos) / 1e9f);}, sekunder, TimeUnit.SECONDS); logger.debug ("Utfører samtale:" + (System.nanoTime () - startNanos) / 1e9f); Svarrespons = call.execute (); logger.debug (Samtalen forventes å mislykkes, men fullføres: "+ (System.nanoTime () - startNanos) / 1e9f, respons);}

13. Responsbufring

Å lage en Cache, trenger vi en cache-katalog som vi kan lese og skrive til, og en begrensning på cache-størrelsen.

Klienten vil bruke den til å cache svaret:

@Test offentlig ugyldig nårSetResponseCache_thenCorrect () kaster IOException {int cacheSize = 10 * 1024 * 1024; File cacheDirectory = new File ("src / test / resources / cache"); Cache cache = ny Cache (cacheDirectory, cacheSize); OkHttpClient-klient = ny OkHttpClient.Builder () .cache (cache) .build (); Be forespørsel = ny Request.Builder () .url ("// publicobject.com/helloworld.txt") .build (); Svarrespons1 = client.newCall (forespørsel) .utfør (); logResponse (respons1); Svarrespons2 = client.newCall (forespørsel) .utfør (); logResponse (respons2); }

Etter at testen er startet, vil ikke svaret fra den første samtalen ha blitt bufret. Et kall til metoden cacheResponse vil returnere null, mens en samtale til metoden networkResponse vil returnere svaret fra nettverket.

Cache-mappen vil også bli fylt med cache-filene.

Den andre samtalekjøringen vil gi den motsatte effekten, ettersom svaret allerede har blitt bufret. Dette betyr at en samtale til networkResponse vil returnere null mens en samtale til cacheResponse vil returnere svaret fra hurtigbufferen.

For å forhindre svar fra å bruke hurtigbufferen, bruk CacheControl.FORCE_NETWORK. For å forhindre at det bruker nettverket, bruk CacheControl.FORCE_CACHE.

Vær advart: hvis du bruker FORCE_CACHE og svaret krever nettverket, OkHttp vil returnere et 504 utilfredsstillende forespørselssvar.

14. Konklusjon

I denne artikkelen har vi sett flere eksempler på hvordan du bruker OkHttp som en HTTP & HTTP / 2-klient.

Som alltid kan eksempelkoden bli funnet i GitHub-prosjektet.