Vodič za OkHttp

1. Uvod

U ovom ćemo članku prikazati osnove slanja različitih vrsta HTTP zahtjeva, primanja i tumačenja HTTP odgovora, i kako konfigurirati klijenta s OkHttp.

Također, idemo u naprednije slučajeve korištenja konfiguriranja klijenta s prilagođenim zaglavljima, vremenskim ograničenjima, predmemoriranjem odgovora itd.

2. OkHttp pregled

OkHttp je učinkovit HTTP i HTTP / 2 klijent za Android i Java programe.

Dolazi s naprednim značajkama poput spremanja veze (ako HTTP / 2 nije dostupan), prozirne GZIP kompresije i predmemoriranja odgovora kako bi se mreža u potpunosti izbjegla za ponovljene zahtjeve.

Također se može oporaviti od uobičajenih problema s vezom i, u slučaju neuspjeha veze, ako usluga ima više IP adresa, može pokušati ponovno zatražiti zamjenske adrese.

Na visokoj je razini klijent dizajniran za blokiranje sinkronih i neblokirajućih asinkronih poziva.

OkHttp podržava Android 2.3 i novije verzije. Za Javu je minimalni zahtjev 1,7.

Nakon ovog kratkog pregleda, pogledajmo nekoliko primjera korištenja.

3. Ovisnost Mavena

Prvo dodajmo knjižnicu kao ovisnost u pom.xml:

 com.squareup.okhttp3 okhttp 3.4.2 

Da biste vidjeli najnoviju ovisnost ove knjižnice, pogledajte stranicu na Maven Central.

4. Sinkroni GET s OkHttp

Da bismo poslali sinkroni GET zahtjev, moramo izraditi Zahtjev objekt zasnovan na a URL i napravite a Poziv. Nakon njegovog izvršavanja vraćamo instancu Odgovor:

@Test public void whenGetRequest_thenCorrect () baca IOException {Zahtjev zahtjeva = novi Request.Builder () .url (BASE_URL + "/ date") .build (); Nazovite poziv = client.newCall (zahtjev); Odgovor odgovora = call.execute (); assertThat (response.code (), jednakTo (200)); }

5. Asinkroni GET s OkHttp

Sada, da bismo napravili asinkroni GET, moramo staviti u red a Poziv. A Uzvratiti poziv omogućuje nam čitanje odgovora kad je čitljiv. To se događa nakon što su zaglavlja odgovora spremna.

Očitavanje tijela odgovora još uvijek može blokirati. OkHttp trenutno ne nudi nijedan asinkroni API za primanje tijela odgovora u dijelovima:

@Test javna void whenAsynchronousGetRequest_thenCorrect () {Zahtjev zahtjeva = novi Request.Builder () .url (BASE_URL + "/ date") .build (); Nazovite poziv = client.newCall (zahtjev); call.enqueue (novi povratni poziv () {public void onResponse (call call, Response response) baca IOException {// ...} public void onFailure (call call, IOException e) {fail ();}}); }

6. GET s parametrima upita

Konačno, za dodavanje parametara upita našem GET zahtjevu možemo iskoristiti HttpUrl.Builder.

Nakon izrade URL-a možemo ga proslijediti našem Zahtjev objekt:

@Test public void whenGetRequestWithQueryParameter_thenCorrect () baca IOException {HttpUrl.Builder urlBuilder = HttpUrl.parse (BASE_URL + "/ ex / bars"). NewBuilder (); urlBuilder.addQueryParameter ("id", "1"); Niz url = urlBuilder.build (). ToString (); Zahtjev za zahtjevom = novi Request.Builder () .url (url) .build (); Nazovite poziv = client.newCall (zahtjev); Odgovor odgovora = call.execute (); assertThat (response.code (), jednakTo (200)); }

7. POST zahtjev

Pogledajmo jednostavan POST zahtjev gdje gradimo RequestBody za slanje parametara "Korisničko ime" i "zaporka":

@Test public void whenSendPostRequest_thenCorrect () baca IOException {RequestBody formBody = new FormBody.Builder () .add ("username", "test") .add ("password", "test") .build (); Zahtjev za zahtjevom = novi Request.Builder () .url (BASE_URL + "/ users") .post (formBody) .build (); Nazovite poziv = client.newCall (zahtjev); Odgovor odgovora = call.execute (); assertThat (response.code (), jednakTo (200)); }

Naš članak Kratki vodič za postavljanje zahtjeva s OkHttp ima više primjera POST zahtjeva s OkHttp.

8. Učitavanje datoteke

8.1. Prenesite datoteku

U ovom ćemo primjeru vidjeti kako prenijeti Datoteka. Prenijet ćemo "test.ext ” datoteka pomoću MultipartBody.Builder:

@Test public void whenUploadFile_thenCorrect () baca IOException {RequestBody requestBody = new MultipartBody.Builder () .setType (MultipartBody.FORM) .addFormDataPart ("file", "file.txt", RequestBody.create (MediaType.parse (Application)). oktet-tok "), nova Datoteka (" src / test / resources / test.txt "))) .build (); Zahtjev za zahtjevom = novi Request.Builder () .url (BASE_URL + "/ users / upload") .post (requestBody) .build (); Nazovite poziv = client.newCall (zahtjev); Odgovor odgovora = call.execute (); assertThat (response.code (), jednakTo (200)); }

8.2. Nabavite napredak prijenosa datoteka

Napokon, da vidimo kako doći do napretka a Datoteka Učitaj. Produžit ćemo RequestBody radi vidljivosti postupka prijenosa.

Prvo, evo načina prijenosa:

@Test public void whenGetUploadFileProgress_thenCorrect () baca IOException {RequestBody requestBody = new MultipartBody.Builder () .setType (MultipartBody.FORM) .addFormDataPart ("file", "file.txt", RequestBody.create (MediaType / application ") oktet-tok "), nova Datoteka (" src / test / resources / test.txt "))) .build (); ProgressRequestWrapper.ProgressListener preslušač = (bytesWritten, contentLength) -> {float postotak = 100f * bytesWritten / contentLength; assertFalse (Float.compare (postotak, 100)> 0); }; ProgressRequestWrapper countingBody = novi ProgressRequestWrapper (requestBody, slušatelj); Zahtjev za zahtjevom = novi Request.Builder () .url (BASE_URL + "/ users / upload") .post (countingBody) .build (); Nazovite poziv = client.newCall (zahtjev); Odgovor odgovora = call.execute (); assertThat (response.code (), jednakTo (200)); } 

Ovdje je sučelje ProgressListener koji nam omogućuje praćenje napretka prijenosa:

javno sučelje ProgressListener {void onRequestProgress (long bytesWritten, long contentLength); }

Ovdje je ProgressRequestWrapper što je proširena verzija RequestBody:

javna klasa ProgressRequestWrapper proširuje RequestBody {@Override public void writeTo (BufferedSink sink) baca IOException {BufferedSink bufferedSink; countingSink = novo CountingSink (sudoper); bufferSink = Okio.buffer (countingSink); delegate.writeTo (baferSink); bufferSink.flush (); }}

Napokon, evo i CountingSink što je proširena verzija ProsljeđivanjeUmivaonik :

zaštićena klasa CountingSink proširuje ForwardingSink {private long bytesWritten = 0; javni CountingSink (delegat umivaonika) {super (delegat); } @Override public void write (Buffer source, long byteCount) baca IOException {super.write (source, byteCount); bytesWritten + = byteCount; listener.onRequestProgress (bytesWritten, contentLength ()); }}

Imajte na umu da:

  • Pri produženju ProsljeđivanjeUmivaonik do "CountingSink", nadjačavamo metodu write () za brojanje zapisanih (prenesenih) bajtova
  • Prilikom produženja RequestBody do "ProgressRequestWrapper “, Nadjačavamo metodu writeTo () da bismo koristili našu "Spremnik za prosljeđivanje"

9. Postavljanje prilagođenog zaglavlja

9.1. Postavljanje zaglavlja na zahtjev

Da biste postavili bilo koje prilagođeno zaglavlje na a Zahtjev možemo se poslužiti jednostavnim addHeader poziv:

@Test public void whenSetHeader_thenCorrect () baca IOException {Zahtjev zahtjeva = novi Request.Builder () .url (SAMPLE_URL) .addHeader ("Content-Type", "application / json") .build (); Nazovite poziv = client.newCall (zahtjev); Odgovor odgovora = call.execute (); response.close (); }

9.2. Postavljanje zadanog zaglavlja

U ovom ćemo primjeru vidjeti kako konfigurirati zadano zaglavlje na samom klijentu, umjesto da ga postavimo na svaki zahtjev.

Na primjer, ako želimo postaviti vrstu sadržaja “Aplikacija / json” za svaki zahtjev moramo postaviti presretača za našeg klijenta. Evo metode:

@Test public void whenSetDefaultHeader_thenCorrect () baca IOException {OkHttpClient client = new OkHttpClient.Builder () .addInterceptor (new DefaultContentTypeInterceptor ("application / json")) .build (); Zahtjev za zahtjevom = novi Request.Builder () .url (SAMPLE_URL) .build (); Nazovite poziv = client.newCall (zahtjev); Odgovor odgovora = call.execute (); response.close (); }

A ovdje je DefaultContentTypeInterceptor što je proširena verzija Presretač:

javna klasa DefaultContentTypeInterceptor implementira Interceptor {presretanje javnog odgovora (lanac Interceptor.Chain) baca IOException {Zahtjev originalRequest = chain.request (); Zahtjev requestWithUserAgent = originalRequest .newBuilder () .header ("Content-Type", contentType) .build (); povratni lanac.proceed (requestWithUserAgent); }}

Imajte na umu da presretač dodaje zaglavlje izvornom zahtjevu.

10. Ne slijedite preusmjeravanja

U ovom ćemo primjeru vidjeti kako konfigurirati OkHttpClient da prestane slijediti preusmjeravanja.

Prema zadanim postavkama, ako se na GET zahtjev odgovori s HTTP 301 premješten trajno preusmjeravanje se automatski slijedi. U nekim slučajevima korištenja to bi moglo biti sasvim u redu, ali sigurno postoje slučajevi upotrebe kada to nije poželjno.

Da bismo postigli ovo ponašanje, kada gradimo svog klijenta, moramo postaviti followRedirects do lažno.

Imajte na umu da će odgovor vratiti znak HTTP 301 statusni kod:

@Test public void whenSetFollowRedirects_thenNotRedirected () baca IOException {OkHttpClient client = new OkHttpClient (). NewBuilder () .followRedirects (false) .build (); Zahtjev za zahtjevom = novi Request.Builder () .url ("// t.co/I5YYd9tddw") .build (); Nazovite poziv = client.newCall (zahtjev); Odgovor odgovora = call.execute (); assertThat (response.code (), jednakTo (301)); } 

Ako uključimo preusmjeravanje s pravi parametar (ili ga potpuno ukloniti), klijent će slijediti preusmjeravanje i test neće uspjeti jer će povratni kôd biti HTTP 200.

11. Isteka vremena

Iskoristite vremenska ograničenja za neuspjeh u pozivu kada je njegov kolega nedostupan. Mrežni kvarovi mogu biti posljedica problema s povezivanjem klijenta, problema s dostupnošću poslužitelja ili bilo čega između. OkHttp podržava povezivanje, čitanje i pisanje isteka vremena.

U ovom smo primjeru izgradili našeg klijenta pomoću a readTimeout od 1 sekunde, dok se URL poslužuje s 2 sekunde kašnjenja:

@Test public void whenSetRequestTimeout_thenFail () baca IOException {OkHttpClient client = new OkHttpClient.Builder () .readTimeout (1, TimeUnit.SECONDS) .build (); Zahtjev za zahtjevom = novi Request.Builder () .url (BASE_URL + "/ delay / 2") .build (); Nazovite poziv = client.newCall (zahtjev); Odgovor odgovora = call.execute (); assertThat (response.code (), jednakTo (200)); }

Imajte na umu da test neće uspjeti jer je vremensko ograničenje klijenta kraće od vremena odziva resursa.

12. Otkazivanje poziva

Koristiti Call.cancel () da biste odmah zaustavili tekući poziv. Ako nit trenutno piše zahtjev ili čita odgovor, an IOException bit će bačen.

Koristite ovo za očuvanje mreže kada poziv više nije potreban; na primjer kada se vaš korisnik udalji od aplikacije:

@Test (očekuje se = IOException.class) javna praznina kadaCancelRequest_thenCorrect () baca IOException {ScheduledExecutorService izvršitelj = Izvršitelji.newScheduledThreadPool (1); Zahtjev za zahtjevom = novi Request.Builder () .url (BASE_URL + "/ delay / 2") .build (); int sekundi = 1; long startNanos = System.nanoTime (); Nazovite poziv = client.newCall (zahtjev); executor.schedule (() -> {logger.debug ("Otkazivanje poziva:" + (System.nanoTime () - startNanos) / 1e9f); call.cancel (); logger.debug ("Otkazani poziv:" + (Sustav .nanoTime () - startNanos) / 1e9f);}, sekunde, TimeUnit.SECONDS); logger.debug ("Izvršavanje poziva:" + (System.nanoTime () - startNanos) / 1e9f); Odgovor odgovora = call.execute (); logger.debug (Očekivalo se da poziv neće uspjeti, ali je dovršen: "+ (System.nanoTime () - startNanos) / 1e9f, odgovor);}

13. Spremanje odgovora

Za stvaranje a Predmemorija, trebat će nam direktorij predmemorije u koji možemo čitati i pisati te ograničenje veličine predmemorije.

Klijent će ga koristiti za predmemoriranje odgovora:

@Test public void whenSetResponseCache_thenCorrect () baca IOException {int cacheSize = 10 * 1024 * 1024; Datoteka cacheDirectory = nova datoteka ("src / test / resources / cache"); Predmemorija predmemorije = nova predmemorija (cacheDirectory, cacheSize); OkHttpClient klijent = novi OkHttpClient.Builder () .cache (cache) .build (); Zahtjev za zahtjevom = novi Request.Builder () .url ("// publicobject.com/helloworld.txt") .build (); Odgovor odgovora1 = client.newCall (zahtjev) .execute (); logResponse (odgovor1); Odgovor odgovora2 = client.newCall (zahtjev) .execute (); logResponse (odgovor2); }

Nakon pokretanja testa, odgovor iz prvog poziva neće biti predmemoriran. Poziv metodi cacheResponse će se vratiti null, dok je poziv metodi networkResponse vratit će odgovor s mreže.

Također, mapa predmemorije bit će ispunjena datotekama predmemorije.

Izvršenje drugog poziva proizvest će suprotan učinak, jer će odgovor već biti predmemoriran. To znači da poziv na networkResponse će se vratiti null dok je poziv na cacheResponse vratit će odgovor iz predmemorije.

Da biste spriječili odgovor da koristi predmemoriju, koristite CacheControl.FORCE_NETWORK. Da biste spriječili upotrebu mreže, koristite CacheControl.FORCE_CACHE.

Upozorite: ako koristite PREDMETA SILE a za odgovor je potrebna mreža, OkHttp vratit će odgovor 504 Nezadovoljavajući zahtjev.

14. Zaključak

U ovom smo članku vidjeli nekoliko primjera kako koristiti OkHttp kao HTTP i HTTP / 2 klijent.

Kao i uvijek, primjer koda može se naći u projektu GitHub.