Month: December 2007

JRES 2007 : l’ensemble des sessions : PDF, vidéos

Posted by – December 28, 2007

Les JRES 2007 livre le résultat des rencontres de novembre 2007.

Les sessions abordent bon nombre de sujets : opensource, réseau, système, sécurité, standards, disponibles au format PDF (diapo, article), ou en vidéo MPG4.

Pour rappel, les JRES sont les journées réseaux de l’enseignement supérieur.

C’est Noël : BlogEngine.NET passe à la version 1.3 et autres releases .NET

Posted by – December 23, 2007

Mad a encore frappé : BlogEngine.NET 1.3

Paint Mono est le portage de l’excellent Paint.NET pour la plateforme Linux. Paint.NET que j’utilise souvent, n’a pas à rougir de ses concurrents (Photoshop et consorts). Seule différence, les sources sont disponibles (155 K lignes de code) et la communauté est très réactive.

Développement Web et flux RSS : optimisations

Posted by – December 19, 2007

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 :

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 :

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 ) :

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, ...
}
}

IsReusable permet de gérer le handler comme un singleton (instancié une fois et réutilisation de l'instance par la suite), on ferait donc attention aux objets instanciés dans l'objet.

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 :

Ressources Web

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

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, …

Livres blancs : CMS, bonnes pratiques, frameworks PHP, référencement, .NET, …

Posted by – December 19, 2007

Smile propose 10 livres blancs sur des thèmes très intéressants :

  • CMS opensource (Drupal, Joomla, EzPublish, …)
  • frameworks PHP
  • 100 bonnes pratiques du Web
  • conception d’applications Web
  • virtualisation opensource
  • .NET

Laissez vos coordonnées, et vous recevrez directement le PDF du livre blanc souhaité, cela se passe sur leur site

Prototype, Ajax, aide à la saisie et touche ENTREE

Posted by – December 17, 2007

Introduction

Le billet suivant souhaite démontrer l’utilisation des événements javascript, notamment avec Prototype, sur un cas d’utilisation d’apparence simple : l’utilisation de la touche ENTREE sans déclencher le post du formulaire, mais uniquement une requête Ajax.

L’exemple se base sur du code ASP.NET et Prototype.

Contexte

Imaginons dans un formulaire la possibilité de rechercher une ou des personnes (par le début du nom par exemple), sous forme d’aide à la saisie, par un appel Ajax qui retourne une liste correspondante à la recherche :

L’utilisateur a le choix : cliquer sur Rechercher ou …valider sa saisie par la touche ENTREE. Dans ce dernier cas, si on ne fait rien, le formulaire entier sera envoyé, ce que l’on ne souhaite pas : on veut envoyer une requête Ajax au serveur, pour avoir une liste de personnes, en sélectionner une puis continuer la saisie du formulaire

Evènements & Prototype

Avec Prototype, nous allons utiliser l’évènement key_press sur le champ de saisie : déclencher une méthode lors de la pression de la touche ENTREE, celle-ci désactivera tous les autres évènements (notamment la validation du formulaire), et exécutera notre fonction Ajax.

Dans la mesure du possible, le javascript se voudra non intrusif (recherche Google) : au lieu de mettre les appels de fonctions sur chaque attribut d’évènement (par exemple onkeypress=”mafonction()” pour un input text, ou un onclick=”mafonction()” sur un href), on les mettra en fin de formulaire et on les chargera tout en une fois. Cela a le mérite de pouvoir le désactiver très facilement, et dans la méthode de développement, de se concentrer sur le formulaire, pour ensuite (une fois que cela fonctionne), s’attaquer à l’ajout du javascript sur le code fonctionnel.

Extrait de code

L’extrait suivant est composé de code ASP.NET, et de code Javascript (Prototype).

<fieldset style="border: 1px solid grey;">
<asp:TextBox runat="server" ID="TxtLibelle"></asp:TextBox>
<asp:HyperLink ID="LinkRecherche" runat="server" Text="Rechercher" NavigateUrl="javascript:void(0);" CssClass="normal"/>

<div id="dSearchresult" style="position: absolute; background-color:White;" class="start">
<div id="dClosed" style="width:100%; text-align:right; margin-bottom: 5px;"><a id="lnkFermer" class="normal"><img src="/images/pict_close.gif" title="fermer" alt="fermer"/></a></div>
<div id="dValues"></div>
<div id="dLoading">
<img src="/images/spinner_bleu.gif" alt="Loading"/>
<span class="normal">Recherche en cours...</span>
</div>
</div>
</fieldset>

<script type="text/javascript">
function recherche()
{
new Ajax.Request('recherche.ashx'
{ method: 'get', parameters:
{searchString:$F('<%=TxtLibelle.ClientID %>'),
action: 'findIndividu'},
onComplete:onSearchComplete
});
}

function initialize () {
Event.observe ('<%=LinkRecherche.ClientID%>','click',recherche);

Event.observe ('<%=TtxtLibelle.ClientID%>','keypress',
function(evt){
if(evt.keyCode==Event.KEY_RETURN)
{
Event.stop(evt);
recherche();
}
}
);
} // initialisation des évènements après que la page est chargée

Event.observe(window, 'load', initialize, false);

</script>

Conclusion

Ce petit exemple montre simplement l’usage du javascript à l’aide de Prototype, qui, généralement est boudé par les développeurs1. Avec l’avènement d’Ajax, le JS est revenu au gout du jour, même si quelques bonnes pratiques sont conseillées pour sa mise en place :

  • utilisation de frameworks/libraires : Prototype, jQuery, …ou d’avatars tel que Prototip
  • que le code soit non intrusif
  • installer Firebug pour le débuggage

1 avouons que la maintenance se révéle souvent hasardeuse

Techdays 2008 : agenda et…Silverlight

Posted by – December 13, 2007

C’est parti, si vous êtes inscrit (avec Windows Live ID, sans Windows Live ID) aux Techdays 2008 (11 au 13 février 2008 au Palais des Congrès à Paris), vous pouvez planifier vos sessions

Et nouveauté cette année, une application Silverlight avec des vidéos des intervenants et autres évangélistes, original !

La réunion

Posted by – December 9, 2007

Depuis le temps que je pratique la réunion et le nombre d’heures de vol, cela méritait bien un billet sur le sujet.

Avant la réunion

Mais qu’est-ce qu’une réunion ? on peut déjà répondre que ce n’est pas une rencontre informelle, qui ressemblerait à un salon de thé, à discuter sans but précis, et sans limite de temps ou d’espace. Même si par expérience des réunions informelles peuvent se créer à l’improviste…

La réunion commence avant même d’y participer, quelques questions qu’il faut se poser avant de la lancer : est-elle nécessaire ? autrement dit, a-t-on besoin de se réunir pour régler telle ou telle question, un mail, ou une courte discussion ne seraient-il pas suffisants ? quel objectif a-t-on à provoquer une réunion : une réunion sans objectif et but précis ne présage rien de bon.

A ce stade, nous avons une raison suffisante et nécessaire de faire une réunion : un objectif (réunion de point projet, d’équipe, de recette, de lancement de tel projet, d’information, de présentation de tel produit, …).

Maintenant que nous savons pourquoi nous avons besoin d’une réunion, il reste pour la préparation à : déterminer les participants et la pertinence de leur présence, ainsi que d’établir un ordre du jour assez exhaustif. Parfois, dans cet ordre du jour, j’inclus la durée de chaque partie, à titre d’indication et pour tenir l’objectif de ne pas dépasser la durée que je me suis fixée (30 mn, 1h30, max 2h).

Invitations

J’ai un objectif, un ordre du jour, des participants. Au niveau logistique, réserver une salle (avec ou sans rétroprojecteur, tableau blanc, etc.). On évitera dans la mesure du possible son bureau : pas de téléphone, pas de mails, pas de visites impromptues.

L’invitation que l’on envoie aux participants (mail, calendrier) contient l’objectif de la réunion, l’ordre du jour, heure de début et de fin.

Pendant

La réunion commence à l’heure et finie à l’heure. Le respect de la durée est important, si l’ordre du jour est trop large alors soit le réajuster, soit planifier une autre réunion. Les retardataires s’installeront et prendront en cours les débats.

Avant de commencer : bien accueillir les arrivants (café, …). Une fois assis, remercier les personnes d’avoir répondu présent, et également toujours rappeler l’ordre du jour afin de s’assurer que tout le monde est d’accord avec ce dernier, quitte à rajouter des points (en restant raisonnable) que souhaiteraient aborder les gens. On demande un volontaire pour rapporter (compte-rendu) l’ensemble des débats.

Toute réunion devrait se dérouler avec bienveillance : cela signifie écoute et respect du point de vue de l’autre, donc on évitera dans la mesure du possible les sarcasmes, ou autre second degré.

Lorsqu’il s’agit de projets, celui-ci aura certainement plusieurs réunions de programmées. Aussi, à la fin de la rencontre, proposer et arrêter la prochaine date (voire plus) de réunion.

Après

Le livrable d’une réunion est constitué par le compte-rendu. Celui-ci contient tous les points abordés, ainsi – important – que le plan d’action (X fait Y) à mettre en oeuvre pour accomplir le ou les objectifs de la réunion. On pourra bien entendu faire recetter le compte-rendu par un ou deux participants pour sa validation avant une diffusion à l’ensemble.

Le compte-rendu permet d’acter les décisions, et surtout de bien s’assurer que tout le monde a bien compris les mêmes choses. Il servira aussi de fil conducteur dans le cas où d’autres réunions sont prévues : rappel de la précédente, ajustement le cas échéant de points mal compris , à modifier, …

Conclusion

Comme on peut le voir, une réunion constitue déjà un projet à elle-seule (un mini projet) dans la méthode : objectif, participants, planification, recette, livrable, qu’il conviendra de bien gérer afin de garantir un maximum d’efficacité. De part cet aspect, entre la préparation, le déroulement et l’après-réunion, prévoyez dans votre agenda de 2h à 1/2 journée voire plus, selon le rôle que vous tenez.

Maintenant, à vos agendas !

Vends appartement 3 pièces à Paris Nation

Posted by – December 5, 2007

[Off topic] Ma femme et moi vendons notre appartement situé à Paris Nation, toutes les informations sur cette page.

Comment crypter les paramètres d’une Querystring – ASP.NET/C#

Posted by – December 4, 2007

Pourquoi encrypter une URL ?

Il peut être intéressant lorsqu’on passe des valeurs en GET dans l’URL1 , où celle-ci pourrait être de la forme http://monsupersite.com/monscript.aspx?user=12&motdepasse=monsuperpasse&droit=2 de ne pas donner accès ou du moins la visibilité des paramètres que l’on passe au script de traitement. Ceci principalement pour des raisons de sécurité : tout internaute a dans l’absolu le moyen de modifier à la main la chaine passée dans la Querystring.

Aussi, pour éviter ça, on va tenter de transformer notre Querystring en une chaîne encryptée, de la forme : http://monsupersite.com/monscript.aspx?qs=YnZEUjZmb0QyY292NnlyQzZQTUo0ek…, ce qui est déjà moins compréhensible par l’être humain. qs contient l’ensemble des paramètres passés à l’origine.

Comment faire ?

Ce dont nous avons besoin :

  • de méthodes qui cryptent et décryptent une chaine, nous utiliserons une classe nommée Crypto (fournie par Obviex, la RijndaelSimple renommée en Crypto) qui encapsule l’algorithme (imprononçable) symétrique Rijndael. La clé utilisée pour le calcul est stockée dans un fichier de configuration (dans notre cas, dans le web.config).
  • de méthodes statiques cryptQ(string qs) et decryptQ(string qs) qui appellent Crypto et renvoient une chaine fonctionnelle pour le querystring au format Base64.

using System.Configuration;
public class QueryStringHelper
{
/// <summary>
/// cryptage d'un valeur (querystring)+ convertion base64
/// Avec l'objectif de protéger une querystring (paras/valeurs)
/// </summary>
/// <param name="val"></param>
/// <returns>la valeur base64</returns>
public static string cryptQ(string val)
{
// la clé cryptKey est sotckée dans le web.config
Crypto crypt = new Crypto(ConfigurationManager.AppSettings["cryptKey"]);
byte[] b = Encoding.Default.GetBytes(crypt.Encrypt(val));
string bstring = Convert.ToBase64String(b);
// les caractères spéciaux à protéger pour la querystring
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
bstring = bstring.Replace("/", "_");
bstring = bstring.Replace("+", "-");
return bstring;
}
/// <summary>
/// désérialisation base64 + décryptage d'une valeur
/// </summary>
/// <param name="val"></param>
/// <returns>la valeur base64</returns>
public static string decryptQ(string val)
{
// on remet en place les caractères protégés dans CryptQ()
val = val.Replace("_", "/");
val = val.Replace("-", "+");
// la clé cryptKey est sotckée dans le web.config
Crypto crypt = new Crypto(ConfigurationManager.AppSettings["cryptKey"]);
byte[] b = Convert.FromBase64String(val);
return crypt.Decrypt(Encoding.Default.GetString(b));
}
}

On peut dès lors utiliser ces 2 méthodes pour sécuriser nos URLs :

Pour l’appel :


string myQS = "user=12&motdepasse";
Response.Redirect(string.Format("monscript.aspx?qs={0}",QueryStringHelper.cryptQ(myQS)));

Pour la réception, dans monscript.aspx :


if(! String.IsNullOrEmpty(Request.QueryString["qs"]) )
string myQSdec = QueryStringHelper.decryptQ(Request.QueryString["qs"]);

myQSdec contiendra la chaine origine. Ce qui serait bien, c’est d’avoir une méthode qui me sépare les clés des valeurs, comme on peut l’avoir avec Request.QueryString, ajoutons une méthode à notre classe :


/// <summary>
/// découpage d'une chaine querystring
/// </summary>
/// <param name="query">chaine de la forme para1=val1&para2=val2&...&paran=valn</param>
/// <returns>un dictionnaire paras/valeurs</returns>
public static IDictionary deFoldQS(string query)
{
IDictionary myparams = new Hashtable();
if (String.IsNullOrEmpty(query))
return myparams;
string[] pairs = query.Split(new char[] { '&' });
foreach (string s in pairs)
{
string[] pair = s.Split(new char[] { '=' });
myparams[pair[0]] = (pair.Length > 1) ? pair[1] : string.Empty;
}
return myparams;
}

On peut alors utiliser nos paramètres simplement lors de la réception dans le script monscript.aspx :


if(! String.IsNullOrEmpty(Request.QueryString["qs"]) )
{
string myQSdec = QueryStringHelper.decryptQ(Request.QueryString["qs"]);
IDictionnary querystring = QueryStringHelper.deFoldQS(myQSdec);
// on utilise les valeurs de nos paramètres
string user = querystring["user"] as string ?? "";
string pwd = querystring["motdepasse"] as string ?? "";
int droit;
if (!int.TryParse((string)p["droit"], out droit))
droit = -1;
}

Ce type de méthode d’encryptage de paramètres d’URL peut s’avérer utile lors de la manipulation critique de valeurs qui ne peuvent être transmises qu’en GET. Bien entendu, on ne s’amusera pas à tout crypter (performances), mais que lorsque cela s’avère nécessaire.

Les fichiers QSUtil.cs et Crypto.cs2 :

QSUtil.cs

Crypto.cs

1 passées en Querystring, c’est à dire par l’appel d’un script où les paramètres et leur valeur sont visibles dans l’URL

2 auquel j’ai ajouté 2 méthodes utilitaires pour avoir des hash SHA256 et MD5SHA256 est préférable car l’algorithme MD5 a été cracké.

Prototype et Scriptaculous compressés

Posted by – December 3, 2007

Prototype et la librairie Scriptaculous pèsent à elles-seules au bas mot 150 ko : 100 ko pour Prototype et le reste selon l’usage de Scriptaculous (effets, contrôles, …). Aussi, il reste toujours intéressant d’optimiser1 un peu le chargement d’une page en prenant une attention particulière sur les éléments suivants : cache (serveur, client, pages, portions de page), viewstate, images, CSS, et javascript.

Pour le dernier point2, le groupe Prototype Core offre une version compressée des 2 librairies, réduisant ainsi leur utilisation à 80 ko, c’est à dire 50 % de leur poids initial.

A l’heure actuelle, nous utilisons la version protopacked_v2.16b.zip3, celle-ci couvre les versions 1.5.1.1 (packer) de Prototype et 1.7.1 b3 (packer) de Scriptaculous. Une version sous forme d’un seul fichier pour les dernières versions est disponible (non testée pour ma part) : protoaculous1.6.packed.js

Publicité : un livre vient de sortir (en anglais) sur les toutes nouvelles versions des librairies (1.6.0 & 1.8.0), écrit par un français (Christophe Porteneuve), plus d’informations ici.

1 promis, dès que le temps me le permet, j’écris un article sur l’optimisation du développement Web, le temps, toujours le temps qui manque ;)

2 on pourrait aussi bien utiliser un module de compression par exemple, qui compresserait à la volée les JS. Dans notre cas, ce module est déjà utilisé, mais la version de l’époque ne permettait pas la compression des JS, mais uniquement du flux d’une page aspx.

3 tourne maintenant sur notre plateforme depuis quelques mois sans encombre