Un guide pour OkHttp
1. introduction
Dans cet article, nous allons montrer 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 clientwith OkHttp.
Nous aborderons également 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 intègre 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 afin d’é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 supporte Android 2.3 et supérieur. Pour Java, la configuration minimale requise est 1.7.
After this brief overview, let’s see some usage examples.
3. Dépendance Maven
Ajoutons d'abord la bibliothèque en tant que dépendance dans lespom.xml:
com.squareup.okhttp3
okhttp
3.4.2
Pour voir la dernière dépendance de cette bibliothèque, consultez la page surMaven Central.
4. GET synchrone avec OkHttp
Pour envoyer une requête GET synchrone, nous devons construire un objetRequest basé sur unURL et créer unCall. Après son exécution, nous récupérons une instance deResponse:
@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 créer un GET asynchrone, nous devons mettre en file d'attente unCall. UnCallback 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 toujours bloquer. OkHttp ne propose 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 des paramètres de requête
Enfin, pour ajouter des paramètres de requête à notre requête GET, nous pouvons tirer parti desHttpUrl.Builder.
Une fois l'URL construite, nous pouvons la transmettre à notre objetRequest:
@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. Exemple de demande de publication
7.1. POST de base
Dans cet exemple simple, nous construisons unRequestBody pour envoyer deux paramètres - «username» et «password» - avec la requête POST:
@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));
}
7.2. POST avec autorisation
Dans cet exemple, nous allons voir comment effectuer un POST avec les informations d'identification de base, et nous envoyons unString comme corps de la requête:
@Test
public void whenSendPostRequestWithAuthorization_thenCorrect()
throws IOException {
String postBody = "test post";
Request request = new Request.Builder()
.url(URL_SECURED_BY_BASIC_AUTHENTICATION)
.addHeader("Authorization", Credentials.basic("test", "test"))
.post(RequestBody.create(
MediaType.parse("text/x-markdown; charset=utf-8"), postBody))
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}
7.3. POST avec JSON
Dans cet exemple, nous allons envoyer une requête POST avec unJSON commeRequestBody:
@Test
public void whenPostJson_then:Correct() throws IOException {
String json = "{\"id\":1,\"name\":\"John\"}";
RequestBody body = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"), json);
Request request = new Request.Builder()
.url(BASE_URL + "/users/detail")
.post(body)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}
7.4. Demande POST en plusieurs parties
Dans cet exemple, nous allons envoyer une demande POST en plusieurs parties. Nous devons construire nosRequestBody en tant queMultipartBody pour publier unFile, un nom d'utilisateur et un mot de passe:
@Test
public void whenSendMultipartRequest_thenCorrect()
throws IOException {
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("username", "test")
.addFormDataPart("password", "test")
.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/multipart")
.post(requestBody)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}
8. Téléchargement de fichiers
8.1. Télécharger un fichier
Dans cet exemple, nous allons voir comment importer unFile. Nous importerons le fichier «test.ext” en utilisantMultipartBody.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éversement deFile. Nous allons étendreRequestBody pour gagner en visibilité sur le processus d'importation.
Premièrement, 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'interfaceProgressListener qui nous permet d'observer la progression du téléchargement:
public interface ProgressListener {
void onRequestProgress(long bytesWritten, long contentLength);
}
Voici leProgressRequestWrapper qui est la version étendue deRequestBody:
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 leCountingSink qui est la version étendue de ForwardingSink:
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 deForwardingSink à“CountingSink”,, nous remplaçons la méthode write () pour compter les octets écrits (transférés)
-
Lors de l'extension deRequestBody à «ProgressRequestWrapper», nous remplaçons la méthode writeTo () pour utiliser notre“ForwardingSink”
9. Définition d'un en-tête personnalisé
9.1. Définition d'un en-tête sur une demande
Pour définir un en-tête personnalisé sur unRequest, nous pouvons utiliser un simple appeladdHeader:
@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. Définition d'un en-tête par défaut
Dans cet exemple, nous verrons comment configurer un en-tête par défaut sur le client lui-même, au lieu de le définir pour chaque requête.
Par exemple, si nous voulons définir un type de contenu“application/json” pour chaque requête, nous devons définir un intercepteur pour notre client. Voici la méthode:
@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();
}
Et voici leDefaultContentTypeInterceptor qui est la version étendue deInterceptor:
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);
}
}
Notez que l'intercepteur ajoute l'en-tête à la demande d'origine.
10. Ne pas suivre les redirections
Dans cet exemple, nous allons voir comment configurer lesOkHttpClient pour arrêter de suivre les redirections.
Par défaut, si une requête GET reçoit une réponse avec unHTTP 301 Moved Permanently, la redirection est automatiquement suivie. Dans certains cas, cela peut être parfaitement correct, mais il y a certainement des cas où cela n’est pas souhaitable.
Pour obtenir ce comportement, lorsque nous construisons notre client, nous devons définirfollowRedirects surfalse.
Notez que la réponse renverra un code d'étatHTTP 301:
@Test
public void whenSetFollowRedirects_thenNotRedirected()
throws IOException {
OkHttpClient client = new OkHttpClient().newBuilder()
.followRedirects(false)
.build();
Request request = new Request.Builder()
.url("http://t.co/I5YYd9tddw")
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(301));
}
Si nous activons la redirection avec un paramètretrue (ou le supprimons complètement), le client suivra la redirection et le test échouera car le code de retour sera un HTTP 200.
11. Délais
Utilisez les délais d'attente pour faire échouer un appel lorsque son homologue est inaccessible. Les défaillances du réseau peuvent être dues à des problèmes de connectivité client, à des problèmes de disponibilité du serveur ou à tout autre problème. OkHttp prend en charge les délais de connexion, de lecture et d'écriture.
Dans cet exemple, nous avons construit notre client avec unreadTimeout de 1 seconde, tandis que l'URL est servie avec 2 secondes de retard:
@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));
}
Notez que le test échouera car le délai d'attente du client est inférieur au temps de réponse de la ressource.
12. Annulation d'un appel
UtilisezCall.cancel() pour arrêter immédiatement un appel en cours. Si un thread est en train d'écrire une requête ou de lire une réponse, unIOException sera lancé.
Utilisez cette option pour conserver le réseau lorsqu'un appel n'est plus nécessaire. Par exemple, lorsque votre utilisateur quitte une 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. Mise en cache des réponses
Pour créer unCache, nous aurons besoin d'un répertoire de cache dans lequel nous pouvons lire et écrire, et d'une limite sur la taille du cache.
Le client l'utilisera pour mettre en cache la réponse:
@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("http://publicobject.com/helloworld.txt")
.build();
Response response1 = client.newCall(request).execute();
logResponse(response1);
Response response2 = client.newCall(request).execute();
logResponse(response2);
}
Après le lancement du test, la réponse du premier appel n'aura pas été mise en cache. Un appel à la méthodecacheResponse renverranull, tandis qu'un appel à la méthodenetworkResponse renverra la réponse du réseau.
En outre, le dossier de cache sera rempli avec les fichiers de cache.
La deuxième exécution de l'appel produira l'effet inverse, car la réponse aura déjà été mise en cache. Cela signifie qu'un appel ànetworkResponse renverranull tandis qu'un appel àcacheResponse renverra la réponse du cache.
Pour empêcher une réponse d'utiliser le cache, utilisezCacheControl.FORCE_NETWORK. Pour l'empêcher d'utiliser le réseau, utilisezCacheControl.FORCE_CACHE.
Attention: si vous utilisezFORCE_CACHE et que la réponse nécessite le réseau,OkHttp renverra une réponse 504 Requête non satisfaisante.
14. Conclusion
Dans cet article, nous avons vu plusieurs exemples d'utilisation de OkHttp en tant que client HTTP et HTTP / 2.
Comme toujours, l'exemple de code se trouve dans lesGitHub project.