Een gids voor OkHttp

1. Inleiding

In dit artikel laten we de basis zien van het verzenden van verschillende soorten HTTP-verzoeken en het ontvangen en interpreteren van HTTP-antwoorden, en hoe u een client configureert met OkHttp.

We zullen ook ingaan op meer geavanceerde gebruiksscenario's van het configureren van een client met aangepaste headers, time-outs, responscaching, enz.

2. OkHttp-overzicht

OkHttp is een efficiënte HTTP- en HTTP / 2-client voor Android- en Java-applicaties.

Het wordt geleverd met geavanceerde functies zoals pooling van verbindingen (als HTTP / 2 niet beschikbaar is), transparante GZIP-compressie en responscaching om het netwerk volledig te vermijden voor herhaalde verzoeken.

Het is ook in staat om te herstellen van veelvoorkomende verbindingsproblemen en, bij een verbindingsfout, als een service meerdere IP-adressen heeft, kan het opnieuw proberen om een ​​ander adres te vragen.

Op een hoog niveau is de client ontworpen om zowel synchrone als niet-blokkerende asynchrone oproepen te blokkeren.

OkHttp ondersteunt Android 2.3 en hoger. Voor Java is de minimumvereiste 1.7.

Laten we na dit korte overzicht enkele gebruiksvoorbeelden bekijken.

3. Maven Afhankelijkheid

Laten we eerst de bibliotheek als afhankelijkheid toevoegen aan het pom.xml:

 com.squareup.okhttp3 okhttp 3.4.2 

Ga naar de pagina op Maven Central om de laatste afhankelijkheid van deze bibliotheek te zien.

4. Synchroon KRIJGEN met OkHttp

Om een ​​synchroon GET-verzoek te verzenden, moeten we een Verzoek object gebaseerd op een URL en maak een Bel. Na de uitvoering krijgen we een exemplaar terug van Reactie:

@Test public void whenGetRequest_thenCorrect () gooit IOException {Request request = new Request.Builder () .url (BASE_URL + "/ date") .build (); Call call = client.newCall (verzoek); Antwoordantwoord = call.execute (); assertThat (response.code (), equalTo (200)); }

5. Asynchroon KRIJGEN met OkHttp

Om een ​​asynchrone GET te maken, moeten we een Bel. EEN Bel terug stelt ons in staat om het antwoord te lezen wanneer het leesbaar is. Dit gebeurt nadat de antwoordheaders gereed zijn.

Het lezen van de antwoordtekst kan nog steeds blokkeren. OkHttp biedt momenteel geen asynchrone API's om een ​​antwoordtekst in delen te ontvangen:

@Test public void whenAsynchronousGetRequest_thenCorrect () {Request request = nieuwe Request.Builder () .url (BASE_URL + "/ date") .build (); Call call = client.newCall (verzoek); call.enqueue (new Callback () {public void onResponse (Call call, Response response) gooit IOException {// ...} public void onFailure (Call call, IOException e) {fail ();}}); }

6. KRIJG met queryparameters

Ten slotte kunnen we, om queryparameters aan ons GET-verzoek toe te voegen, profiteren van het HttpUrl.Builder.

Nadat de URL is gemaakt, kunnen we deze doorgeven aan onze Verzoek voorwerp:

@Test openbare leegte whenGetRequestWithQueryParameter_thenCorrect () gooit IOException {HttpUrl.Builder urlBuilder = HttpUrl.parse (BASE_URL + "/ ex / bars"). NewBuilder (); urlBuilder.addQueryParameter ("id", "1"); String url = urlBuilder.build (). ToString (); Request request = nieuwe Request.Builder () .url (url) .build (); Call call = client.newCall (verzoek); Antwoordantwoord = call.execute (); assertThat (response.code (), equalTo (200)); }

7. POST-verzoek

Laten we eens kijken naar een eenvoudig POST-verzoek waarin we een RequestBody om de parameters te verzenden "Gebruikersnaam" en "wachtwoord":

@Test public void whenSendPostRequest_thenCorrect () gooit IOException {RequestBody formBody = new FormBody.Builder () .add ("gebruikersnaam", "test") .add ("wachtwoord", "test") .build (); Request request = nieuwe Request.Builder () .url (BASE_URL + "/ gebruikers") .post (formBody) .build (); Call call = client.newCall (verzoek); Antwoordantwoord = call.execute (); assertThat (response.code (), equalTo (200)); }

Ons artikel Een beknopte handleiding voor het plaatsen van verzoeken met OkHttp bevat meer voorbeelden van POST-verzoeken met OkHttp.

8. Uploaden van bestanden

8.1. Een bestand uploaden

In dit voorbeeld zullen we zien hoe u een het dossier. We uploaden de 'test.ext ' bestand met MultipartBody.Builder:

@Test public void whenUploadFile_thenCorrect () gooit IOException {RequestBody requestBody = new MultipartBody.Builder () .setType (MultipartBody.FORM) .addFormDataPart ("file", "file.txt", RequestBody.create (MediaType.parse ("application / octet-stream "), nieuw bestand (" src / test / resources / test.txt "))) .build (); Request request = nieuwe Request.Builder () .url (BASE_URL + "/ gebruikers / upload") .post (requestBody) .build (); Call call = client.newCall (verzoek); Antwoordantwoord = call.execute (); assertThat (response.code (), equalTo (200)); }

8.2. Krijg voortgang van het uploaden van bestanden

Laten we tot slot kijken hoe we de voortgang van een het dossier uploaden. We verlengen RequestBody om inzicht te krijgen in het uploadproces.

Ten eerste is hier de uploadmethode:

@Test public void whenGetUploadFileProgress_thenCorrect () gooit IOException {RequestBody requestBody = new MultipartBody.Builder () .setType (MultipartBody.FORM) .addFormDataPart ("file", "file.txt", RequestBody.create (MediaType.parse ("application / octet-stream "), nieuw bestand (" src / test / resources / test.txt "))) .build (); ProgressRequestWrapper.ProgressListener listener = (bytesWritten, contentLength) -> {float percentage = 100f * bytesWritten / contentLength; assertFalse (Float.compare (percentage, 100)> 0); }; ProgressRequestWrapper countingBody = nieuwe ProgressRequestWrapper (requestBody, listener); Request request = nieuwe Request.Builder () .url (BASE_URL + "/ gebruikers / upload") .post (countingBody) .build (); Call call = client.newCall (verzoek); Antwoordantwoord = call.execute (); assertThat (response.code (), equalTo (200)); } 

Hier is de interface ProgressListener waarmee we de voortgang van het uploaden kunnen observeren:

openbare interface ProgressListener {void onRequestProgress (lange bytesWritten, lange contentLength); }

Hier is de ProgressRequestWrapper dat is de uitgebreide versie van RequestBody:

openbare klasse ProgressRequestWrapper breidt RequestBody uit {@Override public void writeTo (BufferedSink sink) gooit IOException {BufferedSink bufferedSink; countingSink = nieuwe CountingSink (sink); bufferedSink = Okio.buffer (countingSink); delegate.writeTo (bufferedSink); bufferedSink.flush (); }}

Eindelijk is hier het CountingSink dat is de uitgebreide versie van ForwardingWastafel :

beschermde klasse CountingSink breidt ForwardingSink {private long bytesWritten = 0; openbare CountingSink (Sink-gedelegeerde) {super (gedelegeerde); } @Override public void write (Buffer source, long byteCount) gooit IOException {super.write (source, byteCount); bytesWritten + = byteCount; listener.onRequestProgress (bytesWritten, contentLength ()); }}

Let daar op:

  • Bij het verlengen DoorsturenSink naar "CountingSink", we overschrijven de methode write () om de geschreven (overgedragen) bytes te tellen
  • Bij het verlengen RequestBody naar "ProgressRequestWrapper “, Overschrijven we de methode writeTo () om onze "ForwardingSink"

9. Een aangepaste koptekst instellen

9.1. Een koptekst instellen op een verzoek

Om een ​​aangepaste koptekst in te stellen op een Verzoek we kunnen een simpele gebruiken addHeader bellen:

@Test public void whenSetHeader_thenCorrect () gooit IOException {Request request = new Request.Builder () .url (SAMPLE_URL) .addHeader ("Content-Type", "application / json") .build (); Call call = client.newCall (verzoek); Antwoordantwoord = call.execute (); response.close (); }

9.2. Een standaardkop instellen

In dit voorbeeld zullen we zien hoe we een standaard header op de Client zelf kunnen configureren, in plaats van deze op elk verzoek in te stellen.

Als we bijvoorbeeld een inhoudstype willen instellen "Application / json" voor elk verzoek moeten we een interceptor voor onze cliënt instellen. Hier is de methode:

@Test openbare void whenSetDefaultHeader_thenCorrect () gooit IOException {OkHttpClient client = nieuwe OkHttpClient.Builder () .addInterceptor (nieuwe DefaultContentTypeInterceptor ("application / json")) .build (); Request request = nieuwe Request.Builder () .url (SAMPLE_URL) .build (); Call call = client.newCall (verzoek); Antwoordantwoord = call.execute (); response.close (); }

En hier is de DefaultContentTypeInterceptor dat is de uitgebreide versie van Onderschepper:

openbare klasse DefaultContentTypeInterceptor implementeert Interceptor {openbaar antwoord onderscheppen (Interceptor.Chain-keten) gooit IOException {Request originalRequest = chain.request (); Request requestWithUserAgent = originalRequest .newBuilder () .header ("Content-Type", contentType) .build (); return chain.proceed (requestWithUserAgent); }}

Merk op dat de interceptor de header aan het oorspronkelijke verzoek toevoegt.

10. Volg geen omleidingen

In dit voorbeeld zullen we zien hoe u het OkHttpClient om te stoppen met het volgen van omleidingen.

Als een GET-verzoek standaard wordt beantwoord met een HTTP 301 permanent verplaatst de omleiding wordt automatisch gevolgd. In sommige gevallen is dat misschien prima, maar er zijn zeker gevallen waarin dat niet gewenst is.

Om dit gedrag te bereiken, moeten we bij het bouwen van onze klant instellen volgRedirects naar false.

Merk op dat het antwoord een HTTP 301 status code:

@Test openbare leegte whenSetFollowRedirects_thenNotRedirected () gooit IOException {OkHttpClient client = nieuwe OkHttpClient (). NewBuilder () .followRedirects (false) .build (); Request request = nieuwe Request.Builder () .url ("// t.co/I5YYd9tddw") .build (); Call call = client.newCall (verzoek); Antwoordantwoord = call.execute (); assertThat (response.code (), equalTo (301)); } 

Als we de omleiding inschakelen met een waar parameter (of verwijder deze volledig), de client zal de omleiding volgen en de test zal mislukken omdat de retourcode een HTTP 200 is.

11. Time-outs

Gebruik time-outs om een ​​oproep te mislukken wanneer de peer niet bereikbaar is. Netwerkstoringen kunnen het gevolg zijn van verbindingsproblemen met de client, problemen met de beschikbaarheid van de server of iets daartussenin. OkHttp ondersteunt time-outs voor verbinden, lezen en schrijven.

In dit voorbeeld hebben we onze klant gebouwd met een readTimeout van 1 seconde, terwijl de URL wordt weergegeven met 2 seconden vertraging:

@Test openbare leegte whenSetRequestTimeout_thenFail () gooit IOException {OkHttpClient client = nieuwe OkHttpClient.Builder () .readTimeout (1, TimeUnit.SECONDS) .build (); Request request = nieuwe Request.Builder () .url (BASE_URL + "/ delay / 2") .build (); Call call = client.newCall (verzoek); Antwoordantwoord = call.execute (); assertThat (response.code (), equalTo (200)); }

Houd er rekening mee dat de test mislukt omdat de time-out van de client korter is dan de responstijd van de resource.

12. Een gesprek annuleren

Gebruik Call.cancel () om een ​​lopend gesprek onmiddellijk te stoppen. Als een thread momenteel een verzoek schrijft of een antwoord leest, kan een IOException zal worden gegooid.

Gebruik dit om het netwerk te sparen wanneer een oproep niet langer nodig is; bijvoorbeeld wanneer uw gebruiker een applicatie verlaat:

@Test (verwacht = IOException.class) public void whenCancelRequest_thenCorrect () gooit IOException {ScheduledExecutorService executor = Executors.newScheduledThreadPool (1); Request request = nieuwe Request.Builder () .url (BASE_URL + "/ delay / 2") .build (); int seconden = 1; lange startNanos = System.nanoTime (); Call call = client.newCall (verzoek); executor.schedule (() -> {logger.debug ("Oproep annuleren:" + (System.nanoTime () - startNanos) / 1e9f); call.cancel (); logger.debug ("Geannuleerde oproep:" + (System .nanoTime () - startNanos) / 1e9f);}, seconden, TimeUnit.SECONDS); logger.debug ("Aanroep uitvoeren:" + (System.nanoTime () - startNanos) / 1e9f); Antwoordantwoord = call.execute (); logger.debug (Call was naar verwachting mislukt, maar voltooid: "+ (System.nanoTime () - startNanos) / 1e9f, response);}

13. Reactie caching

Om een Cache, hebben we een cachemap nodig die we kunnen lezen en waarnaar we kunnen schrijven, en een limiet voor de grootte van de cache.

De klant zal het gebruiken om het antwoord in het cachegeheugen op te slaan:

@Test openbare leegte whenSetResponseCache_thenCorrect () gooit IOException {int cacheSize = 10 * 1024 * 1024; Bestand cacheDirectory = nieuw bestand ("src / test / resources / cache"); Cachecache = nieuwe Cache (cacheDirectory, cacheSize); OkHttpClient client = nieuwe OkHttpClient.Builder () .cache (cache) .build (); Request request = nieuwe Request.Builder () .url ("// publicobject.com/helloworld.txt") .build (); Reactie response1 = client.newCall (verzoek) .execute (); logResponse (antwoord1); Reactie response2 = client.newCall (verzoek) .execute (); logResponse (respons2); }

Na het starten van de test is het antwoord van de eerste oproep niet in de cache opgeslagen. Een oproep aan de methode cacheResponse zal terugkeren nul, terwijl een aanroep naar de methode networkResponse zal het antwoord van het netwerk retourneren.

Ook wordt de cachemap gevuld met de cachebestanden.

De uitvoering van de tweede oproep heeft het tegenovergestelde effect, aangezien het antwoord al in de cache is opgeslagen. Dit betekent dat een oproep naar networkResponse zal terugkeren nul terwijl een oproep naar cacheResponse retourneert het antwoord uit de cache.

Om te voorkomen dat een reactie de cache gebruikt, gebruikt u CacheControl.FORCE_NETWORK. Gebruik om te voorkomen dat het het netwerk gebruikt CacheControl.FORCE_CACHE.

Wees gewaarschuwd: als u gebruikt FORCE_CACHE en het antwoord vereist het netwerk, OkHttp zal een 504 Unsatisfiable Request-antwoord retourneren.

14. Conclusie

In dit artikel hebben we verschillende voorbeelden gezien van het gebruik van OkHttp als een HTTP- en HTTP / 2-client.

Zoals altijd is de voorbeeldcode te vinden in het GitHub-project.