Magazine

Développement Web & flux RSS : optimisations

Publié le 19 décembre 2007 par Olivier Duval

Dans le cadre de la mise en place d’un système de syndication RSS/Atom (contrôle utilisateur, moteur, et rendu des flux à l’aide d’un httphandler), il était important de s’intéresser aux performances d’accès.

Les flux RSS d’une plateforme sont constamment sollicités par de multiples clients (Netvibes, Google Reader, FF, IE, Feedreader, ...), et ce de façon automatique (ie: sans intervention de l’humain). Le nombre de connexion HTTP, et de transferts peuvent être très vite consommateurs de ressources (bande passante, connexions).

Afin d’optimiser au mieux le système, cela passait bien entendu par une bonne gestion du cache (serveur, client/HTTP).

Un schéma (cliquer dessus) sur ce que l’on souhaite mettre en place pour optimiser les allers-venues des requêtes RSS :

Développement Web & flux RSS : optimisations

Etapes d'un flux RSS

Contexte

Un flux RSS/Atom est accessible par un lien de la forme http://localhost/flux.axd?token=xyz. Ce lien est ici implémenté à l’aide d’un HttpHandler1, l’argument token est l’abonnement au flux. Le service sert plusieurs types de flux selon l’abonnement : nouveautés générales du site, flux sur des résultats de recherche (selon critères), des derniers messages de forums, etc…

Le lien ramène soit un flux au format RSS 2.0, soit au format Atom 1.0.

Le code présenté dans le reste du billet est du C#.

Cache serveur (données)

Le service chargé de générer le flux (nouveautés, recherches, ...) utilise un cache serveur, primordial lorsque vous avez des milliers de visites :

  • l’objet Cache est un bon et le moyen le plus simple pour stocker des données en mémoire sur le serveur. Une astuce : cet objet peut-être utilisé en dehors d’une application Web, par exemple dans une application (fenêtrée ou non), voire dans une librairie assembly [chargée dynamiquement par exemple]. Dans ces cas, le contexte Web2 sera embarqué dans ces derniers. Voir cet exemple d’utilisation .
  • l’utilisation de Memcached (du projet Danga) peut-être une alternative , voir ce billet pour du code en C# et Perl.

Cache client, page

Dans le contexte d’un flux Rss, on ne parlera pas de cache client au sens rendu HTML, mais du point de vue HTTP : il ne sert à rien d’envoyer des données si ces dernières n’ont pas changé depuis la dernière visite.

Toutefois, quelques rappels de techniques de cache client.

  • entêtes3 pour jouer sur le cache du navigateur (invalidation, péremption). Cas pour ne pas mettre une page en cache, force donc la recharge, cela peut être une solution pour mettre à jour une liste suite à une suppression par exemple :

Response.CacheControl = "no-cache";
Response.AddHeader("Pragma", "no-cache");
Response.Expires = -1;
Response.ExpiresAbsolute = DateTime.Now.AddDays(-1);

On pourrait également utiliser l’objet Response.Cache :


Response.Cache.SetExpires(DateTime.Now.AddMinutes(-1))
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetNoStore(); 
  • une page ou une portion de page (contrôle utilisateur) peut être rendu directement au navigateur (ie : le rendu HTML est conservé sur le serveur pour être renvoyé directement, sans exécution du code adhoc) : la directive Outputcache remplit bien ce rôle, que cela soit pour la page entière ou pour un fragment de celle-ci (typiquement les contrôleurs utilisateurs qui utiliseraient cette directive).

Je vous invite à consulter l’excellente page sur ce sujet, sur le (tout nouveau tout beau) MSDN.

Cache HTTP

Un flux RSS renvoie des données au format XML (schéma RSS ou Atom). On peut déjà penser que si les éléments du flux n’ont pas été modifiés depuis la dernière visite, il ne sert à rien de les renvoyer.

Parmi les codes de retour HTTP, nous avons la 304. Cette dernière précise au navigateur que le contenu (la pages HTML, les données) qu’il demande n’a pas été modifié [depuis sa dernière visite], dans ce cas, il ne transféra pas le contenu s’il l’a déjà dans son cache.

En résumé, le test a effectué serait :

nonmodifie = VerifEntete()
si(nonmodifie) 
 httpcode = 304 // on dit explicitement au navigateur d'utiliser les données de son cache
sinon
 rechercher_elements_rss()
 positionner_entete_cache()
 renvoyer_elements()  

Les entêtes que nous allons utiliser (transférées entre le client et le serveur) sont ETag (hash) en 1er lieu puis Last-Modified si ETag ne ramène rien.

En 1ère consultation d’un flux, on aura les entêtes suivantes :

Développement Web & flux RSS : optimisations

Entêtes HTTP avant

Lors des prochaines consultations, si le flux n’a pas de nouveaux éléments, on renverra le code 304 au navigateur (ie: qui lui signifie de récupérer dans son cache les données existantes, ici, le flux [XML] RSS ) :

Développement Web & flux RSS : optimisations

Entêtes HTTP après

Ce qui se traduit par le code

/// <summary>
    ///     Handler pour délivrer les flux RSS/Atom
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class flux : IHttpHandler
    {
        private Feed getFeed()
        {
            return new Feed();
        }

        private bool isNotModified(HttpContext context, out DateTime lastModified)
        {
            string eTag, ifModifiedSince;
            Feed myfeed;            
            bool notModified = false;

            // prise en compte de la dernière modification du flux pour le cache HTTP
            myfeed = getFeed();

            lastModified = myfeed.DatePublication;

            // cache navigateur
            eTag = context.Request.Headers["If-None-Match"];
            ifModifiedSince = context.Request.Headers["if-modified-since"];

            if (!String.IsNullOrEmpty(eTag))
                notModified = eTag.Equals(lastModified.Ticks.ToString());
            else
            {
                if (!String.IsNullOrEmpty(ifModifiedSince))
                {
                    if (ifModifiedSince.IndexOf(";") > -1)
                        ifModifiedSince = ifModifiedSince.Split(';').GetValue(0).ToString();

                    DateTime ifModifiedDate;
                    if (DateTime.TryParse(ifModifiedSince, out ifModifiedDate))
                        notModified = (lastModified <= ifModifiedDate);
                }
            }

            return notModified;
        }

        public void ProcessRequest(HttpContext context)
        {
            DateTime lastModified = DateTime.Now;
            bool notModified = false;

            notModified = isNotModified(context, out lastModified);
            // si le flux n'a pas été modifié depuis la dernière visite, alors on renvoie 304
            if (notModified)
            {
                context.Response.StatusCode = (int)HttpStatusCode.NotModified; // 304 not modified
                context.Response.SuppressContent = true;
                context.Response.End();
            }
            else
            {
                // positionnement du cache - ETag + Last-Modified
                context.Response.Cache.SetCacheability(HttpCacheability.Public);
                context.Response.Cache.SetLastModified(DateTime.Now);
                context.Response.Cache.SetETag(lastModified.Ticks.ToString());
            }
        }

        public bool IsReusable
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        ///     classe non fonctionnelle, à titre d'exemple
        /// </summary>
        private class Feed
        {
            private DateTime _datepublication = DateTime.Now;
            public DateTime DatePublication
            {
                get { return _datepublication; }
                set { _datepublication = value; }
            }

            // le reste : les éléments, la génération XML, ...
        }
    }

Outils

Quelques outils pour analyser le trafic HTTP, notamment les entêtes qui nous intéressent dans le cas présent :

  • Fiddler : un utilitaire qui joue le rôle de proxy (intermédiaire entre le client et le serveur), très exhaustif dans ses possibilités d’analyse du trafic. Pratique dans le cas d’IE.
  • Live HTTP Headers extension FF pour afficher en temps réel les entêtes qui transitent entre le client et le serveur.

Aller plus loin

Optimisations

Au niveau optimisation, on pourrait aller plus loin en prenant garde à la congestion que peut générer un trop grand nombre d’accès HTTP, si ces derniers ont des traitements trop longs notamment.

L’idée de base serait de mettre en place l’exécution asynchrone du handler (ou d’une page aspx) afin de libérer, au moins pendant le traitement, le thread du pool ASP.NET, et ainsi éviter de bloquer potentiellement l’arrivée des autres visiteurs (connexions HTTP).

Des articles sur le sujet :

  • sur l’excellent blog dédié à .NET de Loïc Bar
  • sur le MSDN Magazine
Ressources Web

Quelques URL glanées sur le sujet HTTP et RSS :

  • Etag pour l’un (FF), Last-Modified pour l’autre (IE), enfin il parait
  • entêtes ETag et Last-Modified, site d’aide sur le langage…Python
  • la problématique ETag et Last-Modified pour la réduction de la consommation de la bande passante

1 Un HttpHandler est une manière simple de servir des données sur HTTP. J’en fais notamment un usage intensif pour des appels Ajax.

2 un exemple d’utilisation de l’usage de l’objet Cache dans une application Winform.

3 technique déjà utilisée en PHP, ...


Retour à La Une de Logo Paperblog

A propos de l’auteur


Olivier Duval 4 partages Voir son profil
Voir son blog

l'auteur n'a pas encore renseigné son compte l'auteur n'a pas encore renseigné son compte