Notre point de vue : nous sommes Geonode et nous vendons des proxys. Or, la configuration du proxy dans OkHttp est vraiment inhabituelle : l’authentification via le proxy passe par un proxyAuthenticator, et non par un en-tête. Si l’on se trompe à ce niveau, on obtient une erreur 407 qui ressemble à un problème d’identifiants alors qu’il s’agit en réalité d’un problème de configuration. Cette section se trouve vers la fin. Tout ce qui précède fonctionne sans aucun proxy, et si vous vous familiarisez avec la bibliothèque, commencez par l’utiliser avec votre propre connexion.
Où se trouve désormais OkHttp
Il convient de le préciser d’emblée, car les anciens liens ne fonctionnent plus.
Le dépôt d’OkHttp a été déplacé. github.com/square/okhttp
redirige désormais vers github.com/lysine-dev/okhttp
, et le site de documentation qui se trouvait à l’adresse square.github.io/okhttp
renvoie une erreur 404 — le site actuel se trouve à l’adresse lysine.dev/okhttp
. Le projet est activement maintenu et distribué sous licence Apache 2.0, comme vérifié en septembre 2026.
Les coordonnées Maven n’ont pas changé. Il est toujours publié sous le nom « com.squareup.okhttp3:okhttp
» :
implementation("com.squareup.okhttp3:okhttp:5.5.0")
. Il existe également une liste des composants (BOM) permettant de synchroniser les artefacts associés :
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.5.0"))
Configuration requise : Android 5.0+ (niveau d’API 21+) et Java 8+. OkHttp dépend d’Okio pour les E/S et de la bibliothèque standard Kotlin — toutes deux décrites par le projet comme des « petites bibliothèques offrant une forte compatibilité ascendante ». Sur Android, il utilise AndroidX Startup, et si vous désactivez l’initialiseur dans le manifeste, votre application doit appeler ``OkHttp.initialize(applicationContext)`
dans ``Application.onCreate
`.
L’ancienne branche ``3.12.x`
` prend en charge Android 2.3+ et Java 7, et le projet indique clairement que ces plateformes « ne prennent pas en charge TLS 1.2 et ne doivent pas être utilisées ».
Votre première requête
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
Trois éléments de cet exemple de sept lignes sont importants.
**try
-with-resources n’est pas facultatif.** Response
implémente l’interface Closeable
, et une réponse non fermée maintient sa connexion ouverte. Si les fuites sont suffisantes, le pool s’épuise ; à ce stade, les requêtes se bloquent au lieu d’échouer — un symptôme qui ressemble à un problème réseau, mais qui n’en est pas un.
**body().string()
peut être appelée une seule fois.** Elle consomme le flux. L’appeler deux fois provoque une exception, tout comme l’appeler après la fermeture de la réponse. Si vous avez besoin du corps de la réponse plus d’une fois, stockez la chaîne de caractères.
**execute()
est synchrone.** Pour un traitement asynchrone, enqueue()
prend une Callback
et s’exécute sur le pool de threads du dispatcher d’OkHttp.
Une requête POST suit le même schéma avec un corps :
public static final MediaType JSON = MediaType.get("application/json");
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
Partager le client
La décision de conception que l'on prend le plus souvent à tort.
OkHttpClient
gère un pool de connexions et un pool de threads. En créer un par requête revient à gaspiller toutes les connexions que vous auriez pu réutiliser et à créer des threads que vous abandonnez ensuite. Cela fonctionne, mais c'est lent et, en cas de charge importante, cela épuise les ressources.
Créez un seul client pour votre application et partagez-le. Il est par conception « thread-safe » (sécurisé vis-à-vis des threads).
Lorsque vous avez besoin de paramètres différents pour une partie de votre code, ne créez pas un deuxième client à partir de zéro : clonez celui qui existe déjà afin que les pools soient partagés :
OkHttpClient shortTimeout = client.newBuilder()
.readTimeout(5, TimeUnit.SECONDS)
.build();
newBuilder()
produit un client qui partage le pool de connexions et le répartiteur avec son parent, ce qui est exactement ce que vous recherchez.
Pour l’arrêt, en particulier dans les processus de courte durée, libérez explicitement les ressources :
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
Sans cela, une JVM risque de se bloquer pendant la durée de maintien de la connexion du pool avant de se fermer, ce qui rend le débogage particulièrement difficile dans un outil en ligne de commande.
Ce qu’OkHttp ajoute à votre requête
La bibliothèque réécrit les requêtes, et savoir ce qu’elle ajoute permet d’éviter toute confusion.
La documentation est claire : « OkHttp peut ajouter des en-têtes absents de la requête d'origine, notamment Content-Length, Transfer-Encoding, User-Agent, Host, Connection et Content-Type. Il ajoutera un en-tête Accept-Encoding pour la compression transparente de la réponse, sauf si cet en-tête est déjà présent. Si vous avez des cookies, OkHttp ajoutera un en-tête Cookie contenant ces cookies. »
Deux conséquences.
La compression transparente est automatique et s’inverse pour vous. OkHttp demande la compression, décompresse la réponse, puis « supprimera les en-têtes de réponse correspondants Content-Encoding et Content-Length, car ils ne s’appliquent pas au corps de la réponse décompressée ». L’absence d’Content-Lengths dans une réponse est donc normale et ne constitue pas un bug — sauf si vous définissez vous-même Accept-Encoding, auquel cas vous êtes responsable de la décompression.
Les requêtes conditionnelles se produisent automatiquement lorsque la mise en cache est activée. OkHttp ajoute If-Modified-Since et If-None-Match pour revalider les entrées de cache périmées.
Il suit également les redirections par défaut et, en cas de demande d’autorisation, « demandera à l’Authenticator (si celui-ci est configuré) de répondre à la demande », en réessayant avec les identifiants fournis.
La bibliothèque se décrit comme « respectueuse de certains principes et évitant d’être trop configurable, en particulier lorsque cette configuration vise à contourner un serveur bogué, à tester des scénarios invalides ou à enfreindre la RFC applicable ». Elle énonce honnêtement ses propres limites : elle « n’autorise pas les requêtes GET avec un corps » et le cache « n’est pas une interface pouvant faire l’objet d’implémentations alternatives ». Si vous avez besoin d’envoyer délibérément des requêtes invalides, cette bibliothèque n’est pas adaptée à cet usage, et il s’agit là d’un choix de conception plutôt que d’un oubli.
Intercepteurs
Le principal point d’extension, et la fonctionnalité qui déterminera votre utilisation de la bibliothèque.
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
La documentation insiste sur le fait que « l’appel à chain.proceed(request) constitue un élément essentiel de l’implémentation de chaque intercepteur. C’est dans cette méthode, d’apparence simple, que s’effectuent toutes les opérations HTTP. » Et un avertissement à prendre au sérieux : « si chain.proceed(request) est appelé plus d’une fois, les corps des réponses précédentes doivent être fermés. »
Il en existe deux types, et il est important de faire le bon choix. Enregistrez-vous avec addInterceptor() ou addNetworkInterceptor(). La documentation expose précisément la différence.
Intercepteurs d’application :
- « Pas besoin de se soucier des réponses intermédiaires telles que les redirections et les nouvelles tentatives. »
- « Sont toujours invoqués une seule fois, même si la réponse HTTP est servie à partir du cache. »
- « Respectent l’intention initiale de l’application. Ne tiennent pas compte des en-têtes injectés par OkHttp, tels que
If-None-Match. » - « Peuvent court-circuiter l’appel et ne pas appeler
Chain.proceed(). » - « Peuvent réessayer et effectuer plusieurs appels vers
Chain.proceed(). » - « Possibilité d’ajuster les délais d’expiration des appels à l’aide de
withConnectTimeout,withReadTimeout,withWriteTimeout. »
Intercepteurs réseau :
- « Capable d’intervenir sur les réponses intermédiaires telles que les redirections et les nouvelles tentatives. »
- « N’est pas invoqué pour les réponses mises en cache qui contournent le réseau. »
- « Observez les données telles qu’elles seront transmises sur le réseau. »
- « Accès à l’
Connectione qui transporte la requête. »
Règle pratique : utilisez un intercepteur d’application pour tout ce qui concerne l’intention de votre requête — ajout d’un en-tête d’autorisation, d’un agent utilisateur, journalisation au niveau de l’application. Utilisez un intercepteur réseau pour tout ce qui concerne ce qui transite réellement sur le réseau — inspecter les corps compressés, visualiser chaque étape de redirection, examiner la connexion.
L’erreur la plus courante consiste à enregistrer un intercepteur d’authentification en tant qu’intercepteur réseau, ce qui le déclenche alors à chaque étape de redirection et peut entraîner la fuite de vos identifiants vers un hôte auquel vous ne vous attendiez pas.
Délais d'attente
OkHttp propose des valeurs par défaut judicieuses et quatre paramètres distincts ; savoir lequel s'est déclenché vous permet d'identifier l'origine du problème.
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.build();
**connectTimeout
** concerne l'établissement de la connexion TCP et TLS.
**readTimeout
** s'applique entre chaque lecture, et non à l'ensemble de la réponse — ainsi, un téléchargement lent mais progressif ne le déclenche jamais.
**writeTimeout
** fait de même pour les envois.
**callTimeout
** limite l’appel dans son intégralité, y compris les redirections, les nouvelles tentatives et le transfert du corps de la requête. Sa valeur par défaut est zéro, ce qui signifie qu’il n’y a pas de limite globale.
C’est ce dernier paramètre qu’il faut définir. Sans elle, un appel qui ne transmet que des données au compte-gouttes peut s’exécuter indéfiniment, car le délai d’expiration de lecture se réinitialise à chaque octet reçu. callTimeout
est le plafond qui permet à une tâche de se terminer, et il s’agit de l’équivalent de --max-time
dans curl — une distinction que nous avons abordée dans la configuration d’un délai d’expiration avec curl.
Des exceptions par appel sont disponibles via les méthodes ``withReadTimeout`
` et autres d’un intercepteur d’application ; c’est ainsi que vous accordez plus de marge à un point de terminaison lent sans assouplir les valeurs par défaut pour l’ensemble du système.
Proxys
C’est là qu’OkHttp se distingue de la plupart des clients, et c’est là que les utilisateurs perdent du temps.
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("proxy.example.com", 9000));
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.build();
Pour SOCKS, utilisez Proxy.Type.SOCKS en suivant le même principe.
L’authentification est la partie qui surprend le plus les utilisateurs. Vous ne devez pas définir d’en-tête « Proxy-Authorization ». Vous fournissez un proxyAuthenticator, qu’OkHttp invoque lorsque le proxy émet une requête d’authentification 407 :
Authenticator proxyAuth = (route, response) -> {
if (response.request().header("Proxy-Authorization") != null) {
return null; // already tried these credentials; give up
}
String credential = Credentials.basic("user", "pass");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
};
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(proxyAuth)
.build();
Le retour null est essentiel. Sans lui, un mot de passe erroné entraîne une boucle de réessais infinie plutôt qu’un échec — OkHttp interroge l’authentificateur, obtient les mêmes identifiants incorrects, reçoit un autre 407, puis interroge à nouveau. C’est en vérifiant si vous avez déjà envoyé un en-tête « Proxy-Authorization » que vous brisez ce cycle.
Trois remarques supplémentaires.
Un code 407 n’est pas un code 401. Le proxy vous a refusé l’accès et n’a jamais atteint la cible. Un code 401 signifie que le proxy a fonctionné et que la cible demande des identifiants, ce qui correspond à un tout autre scénario d’authenticator().
proxySelector() vous permet de choisir un proxy par requête plutôt que par client, ce qui vous permet d’acheminer différemment les différents hôtes sans avoir à créer plusieurs clients.
Vérifiez que la configuration a bien pris effet. Envoyez une requête à un service qui renvoie votre adresse, avec et sans le proxy configuré. Une mauvaise configuration passe ici inaperçue — les requêtes aboutissent et sont acheminées directement — et la vérification de l’adresse de sortie est le seul moyen d’en avoir la certitude. C’est ce schéma de « succès silencieux » que nous avons abordé dans l’article « Pourquoi il est important de tester les proxys ».
Gérer correctement les réponses
Quelques bonnes pratiques qui font la différence entre un code qui fonctionne et un code qui fonctionne sous charge.
**Vérifiez l’isSuccessful()
, et pas seulement l’absence d’exception.** OkHttp lève des exceptions de type ``IOException`
en cas d’échecs réseau, et non en cas de codes d’erreur HTTP. Un code 404 ou 500 est renvoyé sous la forme d’une exception ``Response
` classique :
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
String body = response.body() != null ? response.body().string() : "";
throw new IOException("HTTP " + response.code() + " from " + request.url()
+ ": " + body.substring(0, Math.min(200, body.length())));
}
return response.body().string();
}
L'inclusion de la première partie du corps de l'erreur transforme un code d'état opaque en un message qui nomme le problème — la plupart des API s'expliquent elles-mêmes dans le corps d'un 400.
Ne mettez pas en mémoire tampon les réponses volumineuses dans une chaîne de caractères (String). body().string()
lit tout en mémoire. Pour un téléchargement volumineux, utilisez un flux :
try (Response response = client.newCall(request).execute();
BufferedSource source = response.body().source();
BufferedSink sink = Okio.buffer(Okio.sink(new File("out.bin")))) {
sink.writeAll(source);
}
Les appels asynchrones nécessitent tout de même la fermeture du corps, et le callback s’exécute sur un thread d’arrière-plan :
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
logger.warn("request failed", e);
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
handle(body.string());
}
}
});
Notez que onFailure
ne se déclenche qu’en cas de problèmes réseau. Une erreur HTTP 500 est gérée par onResponse
, ce qui surprend souvent les utilisateurs qui s’attendent à ce que la dénomination corresponde littéralement à ce qu’elle suggère.
Les tentatives de réessai doivent être gérées avec précaution. OkHttp réessaie automatiquement certaines erreurs au niveau de la connexion, en fonction du paramètre retryOnConnectionFailure()
, qui est activé par défaut. Il ne réessaie pas les codes d’erreur HTTP, et il ne devrait pas le faire — réessayer une requête POST qui a peut-être déjà abouti peut entraîner une écriture en double. Si vous ajoutez votre propre logique de réessai, faites-le dans un intercepteur d’application, limitez le nombre de tentatives, respectez un délai entre chacune d’elles et limitez-vous aux méthodes idempotentes, sauf si l’API fournit une clé d’idempotence.
Autres avantages
En bref, voici les raisons de le choisir.
HTTP/2, dont « la prise en charge permet à toutes les requêtes adressées au même hôte de partager un même socket ». Le pool de connexions, qui « réduit la latence des requêtes (si HTTP/2 n’est pas disponible) ». GZIP transparent. La mise en cache des réponses, qui « évite complètement le réseau pour les requêtes répétées ».
Résilience. Il « se remettra silencieusement des problèmes de connexion courants », et lorsqu’un service dispose de plusieurs adresses, il « tentera d’utiliser d’autres adresses si la première connexion échoue » — ce qui, comme le souligne le projet, « est nécessaire pour IPv4+IPv6 et les services hébergés dans des centres de données redondants ».
TLS moderne, incluant TLS 1.3, ALPN et le « certificate pinning », en utilisant l’implémentation de la plateforme. Sur la JVM, il prend également en charge Conscrypt, qui intègre BoringSSL à Java, utilisé automatiquement s’il s’agit du premier fournisseur de sécurité.
Conformité aux normes. Le projet répertorie les spécifications qu’il respecte : la RFC 9110 pour la sémantique HTTP, la RFC 9111 pour la mise en cache, la RFC 9112 pour HTTP/1.1, la RFC 9113 pour HTTP/2, la RFC 6455 pour les WebSockets et la spécification WHATWG pour les événements envoyés par le serveur. Lorsqu’une spécification est ambiguë, il « suit les agents utilisateurs modernes tels que les navigateurs populaires ou les bibliothèques HTTP courantes ».
Questions fréquentes
OkHttp est-il toujours maintenu ?
Oui. Le dépôt a été déplacé de square/okhttp vers lysine-dev/okhttp et le site de documentation se trouve désormais à l'adresse lysine.dev/okhttp, mais le projet est activement développé et soumis à la licence Apache 2.0. Les coordonnées Maven restent com.squareup.okhttp3:okhttp.
Dois-je créer un nouvel OkHttpClient pour chaque requête ?
Non. Le client dispose d’un pool de connexions et d’un pool de threads, et il est par conception thread-safe. Créez-en un pour votre application et partagez-le. Lorsque vous avez besoin de paramètres différents, utilisez newBuilder() sur le client existant afin que les pools soient partagés.
Pourquoi dois-je fermer la réponse ?
Parce qu’Response maintient une connexion jusqu’à ce qu’elle soit fermée, et que les réponses non fermées épuisent le pool de connexions. Le symptôme est que les requêtes se bloquent au lieu d’échouer, ce qui est difficile à diagnostiquer. Utilisez toujours try-with-resources.
Quelle est la différence entre un intercepteur d’application et un intercepteur réseau ?
Un intercepteur d’application voit votre requête d’origine et est appelé exactement une fois, même pour les réponses mises en cache, et peut court-circuiter ou réessayer. Un intercepteur réseau voit chaque échange réseau individuel, y compris les redirections et les réessais, a accès à la connexion et est entièrement ignoré pour les réponses mises en cache.
Comment configurer un proxy dans OkHttp ?
Transmettez un objet java.net.Proxy à OkHttpClient.Builder.proxy(). Pour l’authentification, définissez un proxyAuthenticator plutôt qu’un en-tête — et renvoyez null lorsqu’un en-tête Proxy-Authorization est déjà présent, sinon des identifiants erronés entraîneront une boucle de réessais infinie.
Quels délais d’expiration dois-je définir ?
connectTimeout : environ 10 secondes, readTimeout et writeTimeout : environ 30 secondes, et — surtout — un délai d’expiration de lecture (callTimeout), dont la valeur par défaut est zéro et qui est le seul paramètre limitant l’ensemble de l’appel. Sans lui, une réponse qui s’affiche lentement ne se solde jamais par un délai d’expiration, car le délai d’expiration de lecture se réinitialise à chaque octet.
OkHttp gère-t-il automatiquement le format GZIP ?
Oui, si vous ne définissez pas vous-même l’en-tête Accept-Encoding. Il demande la compression, décompresse la réponse et supprime les en-têtes Content-Encoding et Content-Length, car ceux-ci ne décrivent plus le corps décompressé. Si vous définissez l’en-tête manuellement, vous vous chargez également de la décompression.
Quelle version de Java est requise pour OkHttp ?
Java 8 ou une version ultérieure, et Android 5.0 (niveau d’API 21) ou une version ultérieure. Il dépend d’Okio et de la bibliothèque standard Kotlin. L’ancienne branche 3.12.x prend en charge Java 7 et Android 2.3, mais ne prend pas en charge TLS 1.2 et ne doit pas être utilisée.
Conclusion
OkHttp est une petite API reposant sur une implémentation bien pensée, et deux bonnes pratiques suffisent pour l’utiliser correctement : partager un seul client dans toute votre application, et fermer chaque réponse avec ``try-with-resources. Ces deux erreurs sont silencieuses et se manifestent finalement par des requêtes qui se bloquent, plutôt que par des erreurs que vous pouvez lire.
C’est sur les intercepteurs que vous passerez le plus de temps, et il vaut mieux bien comprendre la distinction entre intercepteurs d’application et intercepteurs réseau plutôt que d’apprendre par essais et erreurs. Les intercepteurs d’application détectent votre intention et s’exécutent une seule fois ; les intercepteurs réseau détectent chaque étape du parcours et sont ignorés pour les réponses mises en cache. Enregistrer un intercepteur d’autorisation au niveau de la couche réseau est l’erreur classique, et celui-ci se déclenche à chaque redirection.
Définissez une durée d’callTimeout (). Sa valeur par défaut est zéro ; c’est le seul paramètre qui limite l’ensemble d’un appel, et son absence explique pourquoi une tâche qui ne devrait prendre que quelques secondes s’exécute parfois jusqu’à ce qu’un autre processus la force à s’arrêter.
Et si vous suivez une documentation plus ancienne, vérifiez les liens. Le projet a migré vers lysine.dev/okhttp et le site hébergé par Square n’existe plus — bien que les coordonnées de l’artefact restent inchangées, votre fichier de build ne nécessite donc aucune modification.
