Una guida a OkHttp

1. Introduzione

In questo articolo, vi mostreremo le basi di invio diversi tipi di richieste HTTP, ricevere ed interpretare risposte HTTP , e come configurare un client con OkHttp.

Inoltre, esamineremo casi d'uso più avanzati di configurazione di un client con intestazioni personalizzate, timeout, memorizzazione nella cache delle risposte, ecc.

2. Panoramica di OkHttp

OkHttp è un client HTTP e HTTP / 2 efficiente per applicazioni Android e Java.

Viene fornito con funzionalità avanzate come il pool di connessioni (se HTTP / 2 non è disponibile), la compressione GZIP trasparente e la memorizzazione nella cache delle risposte per evitare completamente la rete per richieste ripetute.

È anche in grado di recuperare da problemi di connessione comuni e, in caso di errore di connessione, se un servizio ha più indirizzi IP, può ritentare la richiesta a indirizzi alternativi.

Ad un livello elevato, il client è progettato per bloccare sia le chiamate sincrone che quelle asincrone non bloccanti.

OkHttp supporta Android 2.3 e versioni successive. Per Java, il requisito minimo è 1.7.

Dopo questa breve panoramica, vediamo alcuni esempi di utilizzo.

3. Dipendenza da Maven

Aggiungiamo prima la libreria come dipendenza nel pom.xml :

 com.squareup.okhttp3 okhttp 3.4.2 

Per vedere l'ultima dipendenza di questa libreria controlla la pagina su Maven Central.

4. GET sincrono con OkHttp

Per inviare una richiesta GET sincrona, è necessario creare un oggetto Request basato su un URL ed effettuare una chiamata . Dopo la sua esecuzione, otteniamo un'istanza di Response :

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

5. GET asincrono con OkHttp

Ora, per fare un GET asincrono, dobbiamo accodare una chiamata . Una richiamata ci consente di leggere la risposta quando è leggibile. Ciò accade dopo che le intestazioni di risposta sono pronte.

La lettura del corpo della risposta potrebbe ancora bloccarsi. OkHttp attualmente non offre alcuna API asincrona per ricevere un corpo di risposta in parti:

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

6. GET With Query Parameters

Infine, per aggiungere parametri di query alla nostra richiesta GET possiamo sfruttare HttpUrl.Builder .

Dopo che l'URL è stato creato, possiamo passarlo al nostro oggetto Request :

@Test public void whenGetRequestWithQueryParameter_thenCorrect() throws IOException { HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL + "/ex/bars").newBuilder(); urlBuilder.addQueryParameter("id", "1"); String url = urlBuilder.build().toString(); Request request = new Request.Builder() .url(url) .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(200)); }

7. Richiesta POST

Diamo un'occhiata a una semplice richiesta POST in cui creiamo un RequestBody per inviare i parametri "username" e "password" :

@Test public void whenSendPostRequest_thenCorrect() throws IOException { RequestBody formBody = new FormBody.Builder() .add("username", "test") .add("password", "test") .build(); Request request = new Request.Builder() .url(BASE_URL + "/users") .post(formBody) .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(200)); }

Il nostro articolo Una guida rapida per inviare richieste con OkHttp contiene più esempi di richieste POST con OkHttp.

8. Caricamento di file

8.1. Caricare un file

In questo esempio, vedremo come caricare un file . Caricheremo il file " test.ext" utilizzando MultipartBody.Builder :

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

8.2. Ottieni avanzamento caricamento file

Infine, vediamo come ottenere lo stato di avanzamento di un caricamento di file . Estenderemo RequestBody per ottenere visibilità nel processo di caricamento.

Innanzitutto, ecco il metodo di caricamento:

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

Ecco l'interfaccia ProgressListener che ci permette di osservare l'avanzamento del caricamento:

public interface ProgressListener { void onRequestProgress(long bytesWritten, long contentLength); }

Ecco il ProgressRequestWrapper che è la versione estesa di RequestBody :

public class ProgressRequestWrapper extends RequestBody { @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink bufferedSink; countingSink = new CountingSink(sink); bufferedSink = Okio.buffer(countingSink); delegate.writeTo(bufferedSink); bufferedSink.flush(); } }

Infine, ecco il CountingSink che è la versione estesa di Forwarding Sink :

protected class CountingSink extends ForwardingSink { private long bytesWritten = 0; public CountingSink(Sink delegate) { super(delegate); } @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); bytesWritten += byteCount; listener.onRequestProgress(bytesWritten, contentLength()); } }

Nota che:

  • Quando estendiamo ForwardingSink a "CountingSink", sovrascriviamo il metodo write () per contare i byte scritti (trasferiti)
  • Quando estendiamo RequestBody a " ProgressRequestWrapper ", sovrascriviamo il metodo writeTo () per utilizzare il nostro "ForwardingSink"

9. Impostazione di un'intestazione personalizzata

9.1. Setting a Header on a Request

To set any custom header on a Request we can use a simple addHeader call:

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

9.2. Setting a Default Header

In this example, we will see how to configure a default header on the Client itself, instead of setting it on each and every request.

For example, if we want to set a content type “application/json” for every request we need to set an interceptor for our client. Here is the method:

@Test public void whenSetDefaultHeader_thenCorrect() throws IOException { OkHttpClient client = new OkHttpClient.Builder() .addInterceptor( new DefaultContentTypeInterceptor("application/json")) .build(); Request request = new Request.Builder() .url(SAMPLE_URL) .build(); Call call = client.newCall(request); Response response = call.execute(); response.close(); }

And here is the DefaultContentTypeInterceptor which is the extended version of Interceptor:

public class DefaultContentTypeInterceptor implements Interceptor { public Response intercept(Interceptor.Chain chain) throws IOException { Request originalRequest = chain.request(); Request requestWithUserAgent = originalRequest .newBuilder() .header("Content-Type", contentType) .build(); return chain.proceed(requestWithUserAgent); } }

Note that the interceptor adds the header to the original request.

10. Do Not Follow Redirects

In this example, we'll see how to configure the OkHttpClient to stop following redirects.

By default, if a GET request is answered with an HTTP 301 Moved Permanently the redirect is automatically followed. In some use cases, that may be perfectly fine, but there are certainly use cases where that’s not desired.

To achieve this behavior, when we build our client, we need to set followRedirects to false.

Note that the response will return an HTTP 301 status code:

@Test public void whenSetFollowRedirects_thenNotRedirected() throws IOException { OkHttpClient client = new OkHttpClient().newBuilder() .followRedirects(false) .build(); Request request = new Request.Builder() .url("//t.co/I5YYd9tddw") .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(301)); } 

If we turn on the redirect with a true parameter (or remove it completely), the client will follow the redirection and the test will fail as the return code will be an HTTP 200.

11. Timeouts

Use timeouts to fail a call when its peer is unreachable. Network failures can be due to client connectivity problems, server availability problems, or anything between. OkHttp supports connect, read, and write timeouts.

In this example, we built our client with a readTimeout of 1 seconds, while the URL is served with 2 seconds of delay:

@Test public void whenSetRequestTimeout_thenFail() throws IOException { OkHttpClient client = new OkHttpClient.Builder() .readTimeout(1, TimeUnit.SECONDS) .build(); Request request = new Request.Builder() .url(BASE_URL + "/delay/2") .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(200)); }

Note as the test will fail as the client timeout is lower than the resource response time.

12. Canceling a Call

Use Call.cancel() to stop an ongoing call immediately. If a thread is currently writing a request or reading a response, an IOException will be thrown.

Use this to conserve the network when a call is no longer necessary; for example when your user navigates away from an application:

@Test(expected = IOException.class) public void whenCancelRequest_thenCorrect() throws IOException { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); Request request = new Request.Builder() .url(BASE_URL + "/delay/2") .build(); int seconds = 1; long startNanos = System.nanoTime(); Call call = client.newCall(request); executor.schedule(() -> { logger.debug("Canceling call: " + (System.nanoTime() - startNanos) / 1e9f); call.cancel(); logger.debug("Canceled call: " + (System.nanoTime() - startNanos) / 1e9f); }, seconds, TimeUnit.SECONDS); logger.debug("Executing call: " + (System.nanoTime() - startNanos) / 1e9f); Response response = call.execute(); logger.debug(Call was expected to fail, but completed: " + (System.nanoTime() - startNanos) / 1e9f, response); }

13. Response Caching

To create a Cache, we'll need a cache directory that we can read and write to, and a limit on the cache's size.

The client will use it to cache the response:

@Test public void whenSetResponseCache_thenCorrect() throws IOException { int cacheSize = 10 * 1024 * 1024; File cacheDirectory = new File("src/test/resources/cache"); Cache cache = new Cache(cacheDirectory, cacheSize); OkHttpClient client = new OkHttpClient.Builder() .cache(cache) .build(); Request request = new Request.Builder() .url("//publicobject.com/helloworld.txt") .build(); Response response1 = client.newCall(request).execute(); logResponse(response1); Response response2 = client.newCall(request).execute(); logResponse(response2); }

After launching the test, the response from the first call will not have been cached. A call to the method cacheResponse will return null, while a call to the method networkResponse will return the response from the network.

Also, the cache folder will be filled with the cache files.

The second call execution will produce the opposite effect, as the response will have already been cached. This means that a call to networkResponse will return null while a call to cacheResponse will return the response from the cache.

To prevent a response from using the cache, use CacheControl.FORCE_NETWORK. To prevent it from using the network, use CacheControl.FORCE_CACHE.

Be warned: if you use FORCE_CACHE and the response requires the network, OkHttp will return a 504 Unsatisfiable Request response.

14. Conclusion

In this article, we have seen several examples of how to use OkHttp as an HTTP & HTTP/2 client.

Come sempre, il codice di esempio può essere trovato nel progetto GitHub.