Un guide pour OkHttp

1. Introduction

Dans cet article, nous montrerons les bases de l'envoi de différents types de requêtes HTTP, de la réception et de l'interprétation des réponses HTTP , et comment configurer un client avec OkHttp.

En outre, nous aborderons des cas d'utilisation plus avancés de configuration d'un client avec des en-têtes personnalisés, des délais d'expiration, la mise en cache des réponses, etc.

2. Présentation d'OkHttp

OkHttp est un client HTTP et HTTP / 2 efficace pour les applications Android et Java.

Il est livré avec des fonctionnalités avancées telles que le regroupement de connexions (si HTTP / 2 n'est pas disponible), la compression GZIP transparente et la mise en cache des réponses pour éviter complètement le réseau pour les demandes répétées.

Il est également capable de récupérer des problèmes de connexion courants et, en cas d'échec de connexion, si un service a plusieurs adresses IP, il peut réessayer la demande vers des adresses alternatives.

À un niveau élevé, le client est conçu pour bloquer les appels asynchrones synchrones et non bloquants.

OkHttp prend en charge Android 2.3 et supérieur. Pour Java, la configuration minimale requise est de 1,7.

Après ce bref aperçu, voyons quelques exemples d'utilisation.

3. Dépendance de Maven

Ajoutons d'abord la bibliothèque en tant que dépendance dans le pom.xml :

 com.squareup.okhttp3 okhttp 3.4.2 

Pour voir la dernière dépendance de cette bibliothèque, consultez la page sur Maven Central.

4. GET synchrone avec OkHttp

Pour envoyer une requête GET synchrone, nous devons créer un objet Request basé sur une URL et effectuer un appel . Après son exécution, nous récupérons une instance de 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 asynchrone avec OkHttp

Maintenant, pour faire un GET asynchrone, nous devons mettre un appel en file d'attente . Un rappel nous permet de lire la réponse lorsqu'elle est lisible. Cela se produit une fois que les en-têtes de réponse sont prêts.

La lecture du corps de la réponse peut encore bloquer. OkHttp n'offre actuellement aucune API asynchrone pour recevoir un corps de réponse en plusieurs parties:

@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. OBTENIR avec les paramètres de requête

Enfin, pour ajouter des paramètres de requête à notre requête GET, nous pouvons tirer parti de HttpUrl.Builder .

Une fois l'URL construite, nous pouvons la transmettre à notre objet 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. DEMANDE POST

Regardons une simple requête POST où nous construisons un RequestBody pour envoyer les paramètres «username» et «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)); }

Notre article Un guide rapide pour poster des demandes avec OkHttp contient plus d'exemples de demandes POST avec OkHttp.

8. Téléchargement de fichiers

8.1. Télécharger un fichier

Dans cet exemple, nous verrons comment télécharger un fichier . Nous allons télécharger le fichier « test.ext» à l'aide de 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. Obtenir la progression du téléchargement du fichier

Enfin, voyons comment obtenir la progression d'un téléchargement de fichier . Nous allons étendre RequestBody pour gagner en visibilité sur le processus de téléchargement.

Tout d'abord, voici la méthode de téléchargement:

@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)); } 

Voici l'interface ProgressListener qui nous permet d'observer la progression du téléchargement:

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

Voici le ProgressRequestWrapper qui est la version étendue de 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(); } }

Enfin, voici le CountingSink qui est la version étendue de 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()); } }

Notez que:

  • Lors de l'extension de ForwardingSink à «CountingSink», nous remplaçons la méthode write () pour compter les octets écrits (transférés)
  • Lors de l'extension de RequestBody à « ProgressRequestWrapper », nous remplaçons la méthode writeTo () pour utiliser notre «ForwardingSink»

9. Définition d'un en-tête personnalisé

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.

Comme toujours, l'exemple de code se trouve dans le projet GitHub.