Lorsque l'on développe ou utilise une API, on souhaite que celle-ci puisse être consommée de la manière la plus performante possible; il existe aujourd'hui une mesure technique relativement simple (quand on en remplit les prérequis), qui ne nécessite quasiment aucun développement et n'introduit pas de risque de régression : passer le protocole de communication entre le client et l'API de HTTP 1.1 à HTTP 2.0.

Lors d'une de mes interventions chez un client, nous avons introduit ce changement pour améliorer les performances d'une application mobile grand public faisant un usage intensif d'une API REST/JSON, mais cela s'applique également à une API SOAP. Le passage en HTTP/2 est aussi bénéfique entre un navigateur Web et un site Web, mais cela ne sera pas l'objet de cet article.

Voyons en détail de quoi il retourne...

HTTP/2 : pourquoi ?

Commençons par voir pourquoi le passage de HTTP 1.1 à HTTP 2.0 permet d'améliorer les performances.

HTTP 2.0 (alias HTTP/2) est une évolution du protocole HTTP 1.1, qui change la manière dont les informations sont échangées au niveau de la couche réseau, mais ne change pas la sémantique du protocole (on garde les mêmes verbes, la notion de header et de body, les mêmes codes de retours standardisés).

Le principal changement qui nous intéresse dans l'optique d'un gain de performance depuis une application mobile est la compression des en-têtes HTTP.

En effet, en HTTP 1.x, les en-têtes des requêtes et réponses ne sont pas compressés. Même si le client exprime sa capacité à gérer une compression de l'échange (typiquement par l'ajout de l'en-tête "Accept-Encoding: gzip, deflate" dans la requête), et si le serveur supporte cette même compression, seuls les corps de requête et de réponse sont compressés. Quelque part, ce comportement peut se comprendre car c'est dans les en-têtes que l'on trouve la définition du mode de compression utilisé !

Or, le poids des en-têtes dans une requête ou une réponse HTTP peut être très important, et parfois même plus important que le corps de la requête ou de la réponse !

Prenons le temps de penser à tout ce qui peut être injecté dans les en-têtes d'une requête en regardant un exemple réel tiré de cette application grand public :

GET https://api.acustomer.com/customer/selection/check-list?domain=1 HTTP/1.1
Accept: application/json, application/*+json
Authorization: rI-TRM4I8kyy18LNTKIxzfW9vw3qgGEHkGKOVVsAdddFhTOKKNyMmWZ_pzYebMZURG3jWGliwm1sKJjYiFTw2
ApiKey: 3rH3yFGSSSukovIh5I(...)fjaoIRmjwcVY
AnonymousSession: Ej8IccccS58wX0uJsLk(...)3w0chEXmsfb0_QeYkBAj93nVVKC1xKEVOTj3GD6XAsfd7patVGyCcZwj1dA2
Content-Type: application/json;charset=UTF-8
Content-Length: 8
Host: apiserver2.acustomer.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.10.0

On y retrouve : les types de données acceptées en retour, une clé d'autorisation, une clé d'API, les encodages supportés. C'est long pour un GET qui n'a pas de corps de message !

Et si on fait le même exercice sur les en-têtes d'une réponse :

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
AnonymousSession: Ej8IccccS58wX0uJsLk93mC3w0chEXmsfb0_QeYkBAj93nVVKC1xKEVOTj3GD6XAsfd7patVGyCcZwj1dA2
X-Perfmon-DistributedCache: 1
X-Perfmon-API: 0
X-Server: FRONT-02
X-Release: 0.7.0.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 19 Jul 2018 14:52:23 GMT
Content-Length: 76

{"UserData":{"275274":false},"Success":true,"Message":""}

On y retrouve : des identifiants de session, des informations sur le serveur d'application, la définition de la politique de cache de la ressource, les métadonnées sur le type de contenu de la réponse... tout cela pour récupérer un petit bout de JSON !

Cet exemple, loin d'être extrême, est très courant lorsque l'on utilise des API, et a fortiori quand on a à orchestrer des requêtes entre de multiples micro services, qui ramèneront chacun une partie de la réponse, en faisant ainsi payer de nombreuses fois le poids de ces en-têtes.

J'espère vous avoir convaincu de l'intérêt de passer à HTTP/2, voyons maintenant comment mettre cela en place...

HTTP/2 : comment ?

La négo !

Tout commence par une négociation sur le protocole d'échange entre le client et le serveur, appelée ALPN (Application Layer Protocol Negociation). Il est à noter que cette négociation ne peut se faire que via une connexion chiffrée (en TLS), en qu'on doit donc "être en HTTS" comme on dit dans la vraie vie.

Lors de cette négociation, le client et le serveur vont se mettre d'accord sur la manière d'échanger les données, selon les capacités communes du client et du serveur. Ainsi, seuls les clients supportant HTTP/2 parlant à des serveurs supportant HTTP/2 vont utiliser ce protocole, laissant les composants plus anciens utiliser HTTP 1.1. C'est ainsi qu'on se garantit un premier niveau de non régression dans nos applications.

Il découle de cette contrainte de fonctionnement que nos environnements serveurs et clients vont devoir supporter des prérequis pour que HTTP/2 soit utilisé.

Passons en revue ces contraintes...

Côté serveur

Nous nous concentrerons ici sur les serveurs de la sous-famille Windows/IIS...

Serveur IIS sur site (ou en IaaS)

Que vous utilisiez un serveur IIS sur site ou en IaaS, le prérequis est le même : IIS 10.0 ou supérieur, donc il faut tourner sous Windows Server 2016 (ou Windows 10 pour les environnements de dev)  !

De même, il faut que l'API soit implémentée avec ASP.NET 4.6 ou supérieur, car le support de ce protocole nécessite de modifier des couches réseau au dessus de celles gérées par IIS et Windows.

Une fois ces prérequis remplis, cela fonctionne "out of the box", sans réglage particulier (on ne voit d'ailleurs aucun nouveau paramètre sur ce thème dans la console d'administration d'IIS 10).

PaaS Azure

Si on utilise l'offre PaaS d'Azure pour héberger notre API (ou notre site Web), HTTP/2 n'est pas fonctionnel "out of the box" (ce qui est surprenant, mais passons) : il faut changer le paramétrage par défaut.

Accédez à l'onglet "Paramètres de l'application" de votre ressource de type "App Services", et déplacez le connecteur "version HTTP" à "2.0" (sa valeur par défaut est 1.1).

Et si j'utilise une plateforme d'API Management ?

Si vous êtes en charge d'une API publique, il est fort à parier que votre API repose derrière une brique d'API Management (je vous invite à lire mon article de blog sur ce sujet...), type Azure API Management (chez Microsoft Azure)  ou Amazon API Gateway (chez AWS) et qu'en conséquence ce soit cette brique qui soit attaquée frontalement par votre client en lieu et place de votre API codée en ASP.NET .

Le support de HTTP/2 pour la chaîne client / API passe donc par le support de HTTP/2 dans la brique d'API Management.

Et là, mauvaise nouvelle : à ce jour, ni Azure API Management, ni Amazon API Gateway ne le supportent !

Azure communique (en date du 7 juin 2018 !) travailler sur le support de HTTP/2 entre le client et la gateway dans un premier temps, et implémentera dans un deuxième temps le support entre la gateway et l'API , mais ne fournit pas de date précise.

De son côté, AWS ne communique pas du tout sur le sujet, mais le besoin a été remonté dans ses forums de support.

Azure et AWS jouant au jeu du chat et de la souris quant aux fonctionnalités de leurs services respectifs, gageons que l'arrivée du support chez l'un accélérera la mise à disposition chez l'autre ! Voir le site Good API pour un état du marché sur le sujet...

Testons notre config serveur !

Vous pensez avoir rempli les prérequis côté serveur applicatif ?

L'outil suivant va vous permettre de vérifier si le support de HTTP/2 est bien en place côté serveur :

https://tools.keycdn.com/http2-test

Le résultat avec l'API de test déployée dans le PaaS Azure dans le cadre de la rédaction de cet article ...

Côté client

 

.NET

Dans l'écosystème .NET, la situation est un peu compliquée car la capacité de la classe System.Net.Http.HttpClient à gérer HTTP/2 dépend du runtime utilisé et de l'OS.

Si on utilise le runtime UWP (Universal Windows Platform), HttpClient utilise WinHttpHandler (composant natif Windows), et le protocole HTTP/2 est utilisé avec le serveur dès lors que celui-ci le supporte (Windows 10+), sans avoir à l'expliciter dans le code.

Si on utilise le runtime .NET Core sur Windows, HttpClient utilise WinHttpHandler également, et HTTP/2 est utilisé également... d'après la documentation toutefois, car je n'ai jamais réussi à le faire fonctionner :-(.

Si on utilise le runtime .NET Core sous d'autres OS, on dépend de la capacité de l'OS à le gérer !

Si on utilise Xamarin (Android ou iOS), HttpClient va se reposer sur la capacité de l'OS sous jacent (donc ca va marcher, car les Android & iOS récents le supportent).

Voilà le hic : si on utilise le .NET Framework (app desktop, WPF etc), alors HttpClient utilise  HttpWebRequest, on reste en full managé et HTTP/2 n'est pas supporté. Il existe bien quelques projets opensource visant à combler ce manque, mais ils ne semblent pas aboutis, ou trop complexes en comparaison de HttpClient.

Il faut donc bien étudier l'environnement client pour s'assurer qu'on pourra bien tirer partie de HTTP/2. Mais en résumé, si on reste sur un scénario d'app mobile écrite en .NET (UWP, Xamarin) consommant une API, on est OK !

 

Android / Java (OkHttp)

Le client HTTP OkHttp est souvent utilisé dans les applications natives Android.

HTTP/2 est supporté depuis la version 2.3 de cette librairie, sortie en 2015. Donc les applications récentes n'auront pas avoir de mal à tirer partie de HTTP/2 !

iOS

Pas de souci pour iOS non plus. A partir d'iOS 9, le client nsurlsession supporte HTTP/2.

 

Testons de bout en bout ...

Pour vérifier qu'HTTP/2 est bien utilisé dans votre cas de figure, utilisez simplement le champ Version de votre objet HttpResponseMessage (ou équivalent hors .NET).

Je vous déconseille d'observer le trafic via Fiddler, car il arrive, selon votre configuration OS & réseau, que l'ajout de Fiddler dans la chaine de traitement provoque un fallback vers HTTP 1.1 (en tout cas c'est mon expérience, et on peut passer des heures à essayer de comprendre pourquoi le serveur est OK, le client est OK, le code est OK, mais on ne voit passer que du HTTP/1 !)

Conclusion

J'espère par cet article vous avoir fait comprendre l'intérêt de basculer les échanges entre vos API et vos clients en HTTP/2.

Nous avons vu les prérequis à remplir pour les composants de votre environnement : certificat TLS, Windows Server 2016 et ASP.NET 4.6 côté serveur, et versions minimales du client HTTP côté client. Rien d'insurmontable (hormis le manque de support de HTTP/2 sur les briques d'API Management).

Je ne peux donc que vous inciter à vous lancer dans ce changement pour le bonheur de vos utilisateur et de votre IT !

PS :

Le lecteur attentif et familier du sujet notera que je n'ai pas abordé dans cet article le multiplexage des requêtes sur une connexion unique, l'autre apport de HTTP/2 pour améliorer les performances.

En effet, le gain de performance sur cette fonctionnalité se fait surtout sentir lorsque l'on a un grand nombre de requêtes simultanées vers une API, ce qui n'est pas le cas d'usage classique d'une application mobile consommant une API (où un écran va générer quelques requêtes vers l'API backend, alors qu'une page Web peut générer des dizaines de requêtes vers son backend).

Je n'ai donc pas souhaité m'étendre sur le sujet...


Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *