[ASP.Net] Mettre en place un cache serveur sur votre API
Lors de la création d’API on peut être amené à devoir mettre en place un cache serveur sur certaine méthode (par exemple pour des référentiels). Cette mise en cache est alors pertinente car ces référentiels ne sont pas amenés à changer régulièrement et elle va permettre de ne pas surcharger le serveur inutilement.
Nous allons dans cet exemple utiliser la notion de Http ETag qui est une balise présente dans le header Http qui permet au serveur de connaitre la version des données que détient le client (pour plus d’informations sur le Etag je vous invite à consulter la page Wikipédia : https://en.wikipedia.org/wiki/HTTP_ETag).
Mise en place côté serveur
Dans un controller de mon API, je me suis créé la méthode suivante qui implémente la mise en cache ainsi que l’utilisation de l’Etag.
public HttpResponseMessage Get(Guid playerId) { // On essaye dans un premier temps de récupérer le joueur demandé depuis le cache var player = HttpContext.Current.Cache.Get("player_" + playerId) as Player; // Si le joueur est vide (soit c'est la première demande ou alors le cache a expiré) // on l'obtient depuis notre source de données if (player == null) { player = _playerService.GetPlayer(playerId); if (player == null) { return Request.CreateResponse(HttpStatusCode.NotFound); } // Sauvegarde du joueur dans le cache en lui spécifiant la clé, la durée de rétention, ... HttpContext.Current.Cache.Add("player_" + playerId, player, null, DateTime.Now.AddMinutes(30), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); } // Création de l'ETAG (ici à partir de la date de modification des données du joueur) // pour être capable de savoir si les données ont été modifiées var etag = "\"" + player.ModificationDateTime.Ticks + "\""; if (Request.Headers.IfNoneMatch != null) { if (Request.Headers.IfNoneMatch.Any(requestEtag => requestEtag.Tag == etag)) { // Si l'ETAG est similaire au précédant, les données n'ont pas été modifiées donc on retourne une 304 return Request.CreateResponse(HttpStatusCode.NotModified); } } // Les données ont été modifiées donc on retourne un nouvelle version. // en ajoutant dans les headers la nouvelle valeur de l'ETAG. var response = Request.CreateResponse(HttpStatusCode.OK, player); if (!string.IsNullOrEmpty(etag)) { response.Headers.ETag = new EntityTagHeaderValue(etag); } return response; }
Mise en place côté client
Ici dans une application W8.1 en utilisant HttpClient disponible dans Windows.Web.Http. L’utilisation de cette HttpClient va nous permettre de bénéficier du comportement de Windows via-à-vis des requêtes et notamment la gestion du cache (pour plus d’informations, la documentation MSDN sur le CacheControl : https://msdn.microsoft.com/fr-fr/library/windows/apps/windows.web.http.filters.httpbaseprotocolfilter.cachecontrol.aspx).
Je me suis créé un service qui gère la logique d’obtention depuis la WebApi. Pour l’exemple, je renvoie un tuple avec un booléen et la donnée souhaitée. Le booléen pourrait permettre de sauvegarder (dans une bdd SQLite par exemple), le résultat s’il est différent de celui que l’on possède déjà côté application.
Voici le code :
public class PlayerService { private const string BaseApiUrl = "http://localhost/WebApplication2/api/"; private readonly HttpClient _httpClient; public PlayerService() { // Création d'un HttpClient _httpClient = new Windows.Web.Http.HttpClient(); } public async Task<Tuple<bool, Player>> GetPlayerAsync(Guid playerId) { Debug.WriteLine("Get player : {0}", playerId); var url = BaseApiUrl + "player/" + playerId; var isNewVersion = true; // Requête au serveur var response = await _httpClient.GetAsync(new Uri(url)); // On regarde d'où vient la donnée pour déterminer si c'est une nouvelle version if (response.Source == HttpResponseMessageSource.Cache) { isNewVersion = false; } // On obtient la donnée var result = await response.Content.ReadAsStringAsync(); Debug.WriteLine("Response for player {0} : isNew {1}, result {2}", playerId, isNewVersion, result); // On désérialise la donnée du JSON vers la classe souhaitée avec Newtonsoft var player = await Task.Factory.StartNew(() => JsonConvert.DeserializeObject<Player>(result)).ConfigureAwait(false); // Et on retourne le tuple return new Tuple<bool, Player>(isNewVersion, player); } }
Le résultat dans l’output est le suivant :
Get player : 6104f105-d506-4aa2-b954-dde5bf7effa9 Response for player 6104f105-d506-4aa2-b954-dde5bf7effa9 : isNew True, result {"Id":"6104f105-d506-4aa2-b954-dde5bf7effa9","Name":"Christiano Ronaldo","ModificationDateTime":"2015-06-12T12:15:38"} Get player : 6104f105-d506-4aa2-b954-dde5bf7effa9 Response for player 6104f105-d506-4aa2-b954-dde5bf7effa9 : isNew False, result {"Id":"6104f105-d506-4aa2-b954-dde5bf7effa9","Name":"Christiano Ronaldo","ModificationDateTime":"2015-06-12T12:15:38"} Get player : 6104f105-d506-4aa2-b954-dde5bf7effa9 Response for player 6104f105-d506-4aa2-b954-dde5bf7effa9 : isNew True, result {"Id":"6104f105-d506-4aa2-b954-dde5bf7effa9","Name":"Cristiano Ronaldo","ModificationDateTime":"2015-06-12T12:20:01"} Get player : 6104f105-d506-4aa2-b954-dde5bf7effa9 Response for player 6104f105-d506-4aa2-b954-dde5bf7effa9 : isNew False, result {"Id":"6104f105-d506-4aa2-b954-dde5bf7effa9","Name":"Cristiano Ronaldo","ModificationDateTime":"2015-06-12T12:20:01"}
Je vous rajoute également les traces Fiddler de ces appels :
Examinons de plus près ces quatre appels :
- 1er appel, le serveur retourne le joueur ainsi qu’un 200 qui correspond donc a une donnée nouvelle.
- 2ème appel, le serveur nous a retourné un 304 car l’Etag est similaire au précédent, et le cache Windows nous renvoie automatiquement le joueur depuis le cache.
- 3ème appel, la donnée côté serveur a été modifiée (une faute s’était glissée dans le nom du joueur), le serveur nous retourne donc une 200 avec les nouvelles informations concernant le joueur et le nouvel Etag.
- 4ème appel, le serveur nous a retourné une 304 car l’Etag est similaire au précédent, et le cache Windows nous renvoie automatiquement le joueur depuis le cache.
Windows gère donc pour nous la transmission, la sauvegarde de l’Etag et l’obtention des données depuis le cache.
Grâce à cette utilisation combinée du cache côté serveur, de l’Etag et de l’HttpClient, vous allez pouvoir améliorer les appels à vos API pour ne plus les surcharger inutilement !
Commentaires