Améliorer les performances d’une application web (1er volet)

Savez vous que 47% des internautes s’attendent à ce qu’une page web s’affiche en moins de deux secondes et que 40% des gens vont fermer une page si elle met plus de trois secondes à se charger?  Sur un site de eCommerce le temps d’affichage d’une page a un impact direct sur le taux d’abandon d’un panier.

Dans ce contexte, on comprend pourquoi la performance d’un site web puisse requérir une attention toute particulière. Nous pouvons également avoir la même réflexion pour un site web interne ou un extranet ouvert aux partenaires.

Dans le processus d’amélioration des performances d’une application web on s’intéresse à la fois à ce qui se passe sur le serveur mais également dans la couche cliente (le navigateur). Dans ce premier volet, nous allons nous intéresser plus particulièrement à cette couche cliente : le navigateur.

Il y a de nombreuses technologies qui peuvent être utilisées dans cette couche qui peuvent donner lieu à une attention particulière (applet, flex…), mais nous limiterons notre sujet aux simples requêtes HTTP.

Comment réduire le temps de chargement d’une page web?

1er conseil : Réduire le nombre de requêtes

Cette technique vise à réduire le nombre de requêtes exécutées pour afficher une page. Il s’agit de l’approche la plus importante pour améliorer les performances d’affichage des pages pour les visiteurs qui accèdent au site pour la première fois.

Elle part du constat qu’une grande partie du temps consacré à l’affichage d’une page, ressenti par l’utilisateur d’un site, est passée sur le navigateur et que la majeure partie de ce temps est lié au téléchargement de tous les composants de la page: images, feuilles de style, scripts…

Cette approche met en jeu des techniques permettant de réduire le nombre de ressources à charger. Elle s’appuie notamment sur des solutions de fusion des scripts et des feuilles de style, et de combinaison des images dans un seule image (sprite).
Dans le cas du sprite, c’est à dire du regroupement des images dans une seule image, l’affichage d’une image en particulier est réalisée au travers de la définition de classes CSS qui déclarent notamment une propriété background-position qui précise la position du coin supérieur gauche de l’image que l’on veut afficher. Il faut également définir le fichier image via la propriété background-image ainsi que la taille de l’image à afficher avec les propriétés width et height.

2 ème conseil : Utiliser un CDN Content Delivery Network ou domain Sharding

Les navigateurs limitent volontairement le nombre de requêtes simultanées sur un domaine internet dans le but de ne pas surcharger les serveurs. Les anciens navigateurs autorisaient 2 connexions simultanées (IE7) tandis que les versions plus récentes permettent de réaliser entre 6 et 8 connexions simultanées. Ce dispositif n’empêche pas qu’un site internet chargeant un grand nombre de ressources statiques telles que des images, des scripts, des feuilles de style soit pénalisé par ces limites.

Il existe 2 techniques distinctes permettant d’améliorer le temps de chargement de ces ressources.

Le domain sharding est une technique qui consiste à charger les ressources statiques à partir de sous domaine. Cette technique permet donc de multiplier le nombre de requêtes simultanées par le nombre de sous domaine. L’approche consiste donc à distribuer les ressources entre différents domaines de façon à dépasser les limitations imposées par les navigateurs.
L’inconvénient est que chaque sous domaine nécessite de mettre en oeuvre une résolution de domaine qui peut ajouter un temps de latence supplémentaire. Sur les mobiles, la résolution DNS est également plus longue que pour les connexions dites conventionnelles.

La résolution DNS est le procédé de conversion d’un nom de domaine (par exemple google.com) en une adresse IP.

Il faut toutefois garder à l’esprit que cette technique peut être contre productive dans le cas ou le site web réalise peu de requêtes vers des ressources statiques. Notez également que le protocole HTTP/2.0 rend cette pratique est obsolète.

Le CDN (Content Delivery Network) est un réseau de serveurs distribués sur la planète qui délivre du contenu aux utilisateurs en fonction de la situation géographique des visiteurs. Cette approche consiste donc à distribuer les ressources sur un réseau de serveurs distribués dans le monde, et peut être intéressante pour des sites qui ont une audience internationale. Plusieurs sociétés se répartissent les parts de marché dans ce domaine citons par exemple Akamai, Amazon CloudFront….

3ème conseil : Utiliser le cache des navigateurs

L’utilisation d’un cache pour les ressources statiques a un impact majeur sur les performances et l’expérience utilisateur.  En effet bien que cette technique n’ait aucune incidence lors de la première visite d’un internaute sur la page de votre site, elle permet de réduire l’utilisation de la bande passante et le niveau de sollicitation des serveurs mais également améliore sensiblement le temps de chargement des pages lors des visites successives.
Bien entendu, l’utilisation du cache doit être réservée aux ressources statiques telles que les images, les feuilles de style, les scripts et ne doit surtout pas être utilisée pour gérer du contenu dynamique.
L’idée est de paramétrer l’entête HTTP « expires » de façon à ce que la ressource soit mise en cache sur le navigateur pendant la période déclarée. Lorsque la ressource est expirée (ou si l’utilisateur demande un rafraîchissement de la page « soft-refresh »), si les entêtes HTTP Etag ou Last-Modified sont présents sur la ressource, le navigateur lance une requête conditionnelle avec les entêtes If-None-Match ou If-Modified-Since.
Si le serveur répond avec un status HTTP 304 (Not modified) avec les entêtes relatives à la mise en cache de la ressource, le navigateur gardera la ressource dans le cache et modifiera la date d’expiration conformément aux informations renvoyées par le serveur.
La ressource est, bien entendu servie, par le cache du navigateur.

Pour les ressources statiques mises en cache qui peuvent être modifiées périodiquement, il existe des techniques de gestion de version qui permettent de prendre en compte la modification en forçant le navigateur à charger la nouvelle ressource.

4ème conseil : Compresser les contenus

Le temps de chargement d’une ressource dépend en particulier de la taille du contenu. Un navigateur prendra plus de temps pour charger une ressource de 100 Ko qu’une ressource de 10 Ko 😉 . Cette technique répond à cette exigence de réduction de la taille des ressources en utilisant une technique de compression.
Les ressources textuelles sont principalement visées par cette approche : les documents HTML, les feuilles de style, les scripts…

5ème conseil : Déplacer les scripts à la fin de la page

Cette technique consiste à déclarer les scripts juste avant la fermeture du tag BODY de la page. En effet, l’une des caractéristiques des scripts est qu’ils s’exécutent à l’emplacement exact où ils sont insérés dans le document et de fait, ils bloquent la construction du DOM (Document Object Modèle) et retarde l’affichage initial de la page. Autrement dit, un script déclaré au milieu du contenu d’une page bloque l’affichage du reste de la page tant que le script n’est pas exécuté. Cela peut provoquer des situations de page blanche, ou des effets visuels disgracieux ou l’utilisateur voit le chargement partiel de la page puis attend le rendu complet.

Plus techniquement, lorsque l’analyseur HTML rencontre une balise de script, il suspend le processus de construction du DOM, puis cède le contrôle au moteur JavaScript. Lorsque celui-ci a fini de s’exécuter, le navigateur reprend sa tâche au point où il l’a laissée, puis reprend la construction du DOM. Pourquoi la construction du DOM est-elle interrompue? Tout simplement car le script peut modifier le DOM et le CSSOM (représentation objet d’une feuille de style) qui sont tout deux nécessaires à la construction du rendu de la page.

Par ailleurs, si le navigateur n’a pas fini de construire le CSSOM au moment ou un script peut être exécuté, il retardera l’exécution du script jusqu’à ce que le CSSOM soit construit.

Il est donc nécessaire de charger les feuilles de style le plus tôt possible dans le processus de construction de la page. En fait, cette étape fait appel à une autre notion « le chemin critique du rendu » que nous aborderons dans un autre volet.

Le chemin critique du rendu fait appel à l’optimisation du schéma de dépendance entre HTML, CSS et JavaScript et, plus particulièrement, il s’agit d’améliorer les performances de sorte que le navigateur charge très rapidement tous les éléments graphiques qui sont visibles immédiatement afin que l’utilisateur ait une sensation d’instantanéité. Les éléments non visibles sont chargés d’une façon imperceptible pour l’utilisateur.

Notons simplement à ce stade qu’il existe des attributs de la balise script qui permettent de rendre un script non bloquant.

  • defer : différer l’exécution à la fin du chargement du document.
  • async : charger/exécuter les scripts de façon asynchrone (par un autre thread).

Nous reviendrons plus précisément, dans un prochain volet, sur les mécanismes mis en oeuvre lors du chargement des scripts.

6ème conseil : Externaliser les ressources

Les ressources de type feuille de style et script devraient être externalisées dans des fichiers de façon à bénéficier de l’utilisation du cache du navigateur.

7ème conseil : Réduire l’impact de la résolution DNS

Pour réduire l’impact de la résolution DNS, il est nécessaire d’utiliser la directive HTTP keep-alive et rationaliser l’utilisation des domaines différents.

La directive Keep-alive est un dispositif permettant d’utiliser une simple connexion TCP pour envoyer et recevoir de multiples requêtes et réponses.
Elle s’oppose à un fonctionnement ou chaque requête nécessite l’ouverture et la fermeture d’une connexion TCP. Avec le protocole HTTP/1.1 toutes les connexions sont considérées persistantes par défaut.

8ème conseil : Minifier les scripts

Cette opération consiste à réduire la taille des scripts en supprimant tous les caractères non indispensables (commentaires, espaces inutiles…). Elle peut venir en complément de la technique de compression pour réduire la taille de la ressource.

En synthèse

L’application de ces premières mesures devraient vous permettre d’améliorer le ressenti de l’utilisateur. Dans un prochain volet, nous nous intéresserons à la notion de chemin critique du rendu et nous entrerons plus en détails sur l’impact des scripts sur la performance de l’affichage des pages.

http://www.jperf.com

Publié dans performance | Laisser un commentaire

Qu’est ce que le PACING?

Dans le domaine des tests de performance, il s’agit du temps minimum entre chaque itération d’un scénario de test.

Qu’est ce qu’une itération?

Imaginez que vous recherchiez un objet sur un site d’annonces en ligne. Votre scénario de test pourrait consister en un accès à la page d’accueil du site, le choix de critères de sélection, le parcourt de la liste des objets correspondants à vos critères, et la consultation des annonces les plus intéressantes. Malheureusement vous n’avez rien trouvé et vous quittez le site en fermant simplement votre navigateur. L’exécution de ce scénario une seule fois représente une itération.

Le pacing correspond donc au temps séparant deux exécutions d’un scénario par un même utilisateur virtuel. Il peut être égal ou supérieur au temps d’exécution du scénario. Dans le cas ou il est supérieur, un délai est ajouté à la fin de l’exécution de l’itération avant de commencer l’itération suivante. Si le pacing est inférieur ou égal au temps d’exécution du scénario, l’itération suivante débute immédiatement après la fin de la précédente.

A quoi cela sert il?

Ce mécanisme permet de réaliser des tests de performance avec un taux constant de transactions/requêtes. De fait, lorsque le pacing est calculé correctement, à chaque itération tous les utilisateurs virtuels commencent un nouveau scénario en même temps. Personnellement j’utilise assez peu cette méthode de simulation de charge qui peut produire des graphes d’utilisation des ressources en dents de scie puisque à la fin des itérations tous les utilisateurs virtuels attendent quelques secondes avant de démarrer une nouvelle itération.

http://www.jperf.com

 

Publié dans performance, Uncategorized | Tagué , | Laisser un commentaire

Hibernate: Comment tracer les requêtes SQL et les paramètres des requêtes

Cet article est davantage un pense bête personnel.

Ces deux articles expliquent parfaitement les bonnes pratiques en matière de debugage des requêtes SQL et la manière de les mettre en oeuvre.

Mieux vaut éviter d’utiliser la propriété show_sql et préférer une configuration des logs (org.hibernate.SQL, org.hibernate.type.descriptor.sql …).

http://www.thoughts-on-java.org/hibernate-logging-guide/

http://docs.jboss.org/hibernate/orm/4.3/topical/html/logging/Logging.html

Cependant vous pouvez rencontrer des problèmes de mise en oeuvre si vous utilisez une librairie de log telle que SLF4J. Pourquoi ? Parce que Hibernate (depuis la version 4) utilise un « bridge » pour ne pas être dépendant d’une librairie de log particulière. Ce bridge teste les API disponibles dans le classpath selon un ordre précis. Donc assurez vous bien que les librairies tierces que vous avez intégrées dans votre projet n’ont pas de dépendance avec une API de log.

Dans un projet maven, il suffit d’exécuter la tâche dependency-tree qui liste l’ensemble des dépendance de votre projet. Si vous trouvez une dépendance avec une autre librairie de log, il faudra exclure cette dépendance en intègrant une section exclusion dans le noeud dependency.

Par exemple pour exclure une dépendance avec la librairie log4j.

<dependency>
...
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>

 

Publié dans tech notes | Tagué | Laisser un commentaire

Terminaison SSL avec Apache2/Tomcat/Shiro

Cet article est une note technique permettant de mettre en oeuvre une terminaison SSL dans un environnement Apache2 / Tomcat / Shiro+Spring.

Apache Shiro est une librairie permettant de mettre en oeuvre des fonctions de sécurisation d’une application web. Elle présente approximativement les mêmes fonctionnalités que la solution Spring security. On peut noter toutefois qu’elle permet de sécuriser également des applications standalone, qu’elle intègre nativement la fonction « remember me », etc.

Quelle est précisément la problématique?

Dans une infrastructure de production, l’accès à certaines ressources peuvent être protégées par un protocole sécuritaire (SSL) mais les autres composants de l’infrastructure qui sont déployés dans une zone de confiance n’ont pas vocation à utiliser ce protocole qui ajoute un « overhead » qui n’est pas nécessaire dès lors que les communications sont réputées fiables.

Dans cet article, je détaillerai la mise en oeuvre de ce mécanisme (terminaison SSL) dans un contexte Shiro / spring qui présente une petite difficulté.

La mise en oeuvre de Shiro avec Spring n’est pas l’objet de cette note, cependant voici un extrait de la configuration permettant d’activer Shiro et de configurer l’utilisation de SSL pour sécuriser l’accès à certaines ressources de l’application. C’est le filtre Shiro qui porte cette configuration.


<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/connexion" />
<property name="filters">
<util:map>
<entry key="authc">
<bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter" />
</entry>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/javax.faces.resource/** = anon
/connexion = ssl[${ssl.port}],authc
/moncompte = user
/** = anon
</value>
</property>
</bean>

Nous notons simplement que l’utilisation du filtre PassThruAuthenticationFilter est intéressant car il permet de mettre en place une logique de validation spécifique à l’application. Ce filtre n’autorise l’accès aux ressources que si l’utilisateur est authentifié ou lorsque la ressource demandée correspond à la page d’authentification. Dans cette configuration la ressource identifée par « /connexion » nécessite la mise en oeuvre du protocole SSL. Cette vérification est assurée par la déclaration du filtre SSL (ssl[${ssl.port}]) implémentée par défaut par la classe org.apache.shiro.web.filter.authz.SslFilter.
L’accès à la ressource « /moncompte » est protégée par le filtre UserFilter qui par défaut vérifie que l’utilisateur est connu soit parce qu’il est authentifié soit parce que la fonction « remember me » permet d’identifier l’utilisateur.

Avec cette configuration, si l’utilisateur n’est pas identifié et essaye d’accéder à la ressource « /moncompte », il va être automatiquement redirigé vers la page de login protégée par le protocole SSL. Cependant on souhaite mettre en place une terminaison SSL au niveau du reverse proxy Apache, mais on veut également s’assurer que la requête d’authentification est sécurisée. Or que fait le filtre SSL de Shiro? Il vérifie que la requête utilise le port SSL configuré par la variable ${ssl.port}, et que la requête est sécurisée. Shiro utilise notamment la méthode ServletRequest.isSecure() pour vérifier que la requête est effectivement sécurisée.

Si la configuration du reverse proxy redirige uniquement les hôtes virtuels déclarés pour les protocoles (http et https) vers un port non sécurisé d’un serveur Tomcat, et que l’un de ces critères n’est pas respecté, les mécanismes de redirection mis en jeu provoquent une erreur HTTP 310 « Too many Redirects ».

Un des moyens permettant de mettre en place la terminaison SSL et d’indiquer à la librairie que la requête d’authentification est effectivement sécurisée, consiste à créer une entête HTTP spécifique indiquant que le reverse proxy a détecté l’utilisation d’un protocole sécurisé et de configurer Tomcat afin qu’il interprète cet entête et intègre ces informations dans le contenu de la requête.

Pour mettre en place cette logique, il est nécessaire :

  • sur le reverse proxy
    Activer le module header (sudo a2enmod headers);
    Mettre en place les directives d’écriture du header « X-Forwarded-Proto » sur chaque hôte virtuel;
    Par exemple pour un serveur Tomcat écoutant à l’adresse localhost:9090

    <VirtualHost *:80>
    ServerName www.mondomaine.com
    ServerAlias www.mondomaine.com
    ServerAdmin webmaster@mondomaine.com
    <span style="color: #ffcc00;">RequestHeader set X-Forwarded-Proto "http"</span>
    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass / http://localhost:9090/
    ProxyPassReverse / http://localhost:9090/
    ....
    <VirtualHost *:443>
    ServerName www.mondomaine.com
    ServerAlias www.mondomaine.com
    ServerAdmin webmaster@test.mondomaine.com
    <span style="color: #ffcc00;">RequestHeader set X-Forwarded-Proto "https"</span>
    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass / http://localhost:9090/
    ProxyPassReverse / http://localhost:9090/
    ...
    
  • Sur le serveur Tomcat
    Définir une valve dans la configuration du serveur Tomcat qui permet d’interpréter cet entête HTTP.
    Par exemple:

    <Valve>
    className="org.apache.catalina.valves.RemoteIpValve"
    protocolHeader="x-forwarded-proto"
    protocolHeaderHttpsValue="https"
    httpServerPort="80"
    httpsServerPort="443"
    />
    

    Ces paramètres indiquent notamment :
    « protocolHeader » le nom de l’entête HTTP qui désigne la nature du protocole utilisé. Dans cet article l’entête « x-forwarded-proto » porte cette définition;
    « protocolHeaderHttpsValue » la valeur de l’entête « protocolHeader » qui identifie que le protocole est sécurisé;
    ainsi que d’autres attributs dont vous pouvez avoir besoin qui sont décrits dans la documentation officielle (https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html).

Une fois ces modifications réalisées et les serveurs redémarrés, l’erreur HTTP 310 « Too many Redirects » que nous rencontrions précédemment disparaît, et la terminaison SSL sur le reverse proxy est opérationnelle.

http://www.jperf.com

Publié dans tech notes | Tagué , , , , | Laisser un commentaire

Integration Apache Shiro et PrettyFaces

Shiro est une librairie java qui peut être utilisée pour gérer la sécurité d’une application web, standalone, ou mobile. Elle prend en charge l’authentification, les autorisations d’accès aux ressources, la cryptographie, et la gestion des sessions (cf http://www.infoq.com/articles/apache-shiro).

PrettyFaces est une librairie open source permettant de réaliser de la ré-écriture d’URL qui s’intègre avec la technologie JSF. Elle permet entre autre de créer des URLs qui peuvent être « bookmarquer »  (http://www.ocpsoft.org/prettyfaces/).

L’intégration de ces deux technologies est réellement très simple. Il suffit de déclarer les filtres nécessaires aux 2 librairies dans l’ordre suivant:

1/ filtre Shiro

Si vous utilisez Spring la déclaration est la suivante

Dans le descripteur de déploiement de l’application web « web.xml »


<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>

Dans le fichier de configuration de spring


<!-- BEGIN OF SHIRO CONFIGURATION -->

<!-- Enable Shiro Annotations for Spring-configured beans. only run after -->
<!-- the lifecycleBeanProcessor has run: -->

<!-- AUTO PROXY -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>

<!-- ADVISOR -->
<bean id="authorizationAttributeSourceAdvisor" class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>

<!-- SECURITY MANAGER -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- Single realm app. If you have multiple realms, use the 'realms' property instead. -->
<property name="realm" ref="myRealm" />
</bean>

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

<!-- SHIRO FILTER -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/connexion" />
<property name="successUrl" value="/welcome" />
<property name="unauthorizedUrl" value="/err404.jsp" />
<property name="filters">

<map>

</map>

</property>
<property name="filterChainDefinitions">
<value>
<!-- /connexion = ssl[8443],authc /accueil = anon /pages/** = authc *.xhtml = user -->
</value>
</property>
</bean>
<bean id="myRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="dataSource" ref="madatasource" />
<property name="authenticationQuery" value="select password from user where email = ?" />
<property name="userRolesQuery" value="select role_name from user_roles where username = ?" />
<property name="permissionsQuery" value="select permission from roles_permissions where role_name = ?" />
</bean>

<!-- END OF SHIRO CONFIGURATION -->

2/ filtre PrettyFaces

La définition du filtre PrettyFaces peut être réalisée comme suit dans le fichier web.xml.

</pre>
<blockquote>
<filter>
<filter-name>Pretty Filter</filter-name>
<filter-class>com.ocpsoft.pretty.PrettyFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>Pretty Filter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ASYNC</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<pre>

Prenez garde à respecter l’ordre de déclaration de sorte que PrettyFaces puisse ré-écrire les URLs. Dans Shiro vous pouvez utiliser la nouvelle URL générée par PrettyFaces.

Dans la configuration Shiro / spring, il n’est pas nécessaire d’utiliser le fichier de configuration shiro.ini. La configuration est réalisée directement dans le fichier de configuration de spring.

Publié dans tech notes | Tagué , | Laisser un commentaire

Problème de chargement des fontes « Failed to decode downloaded font »

Voici un lien vers un article permettant de résoudre cette problématique sur des projets utilisant maven

http://shortfastgood.blogspot.fr/2015/08/maven-build-and-webfonts.html

Publié dans tech notes, Uncategorized | Laisser un commentaire

Font Loading Techniques

Cette page est simplement un signet vers un site exposant les techniques d’amélioration des performances lors du chargement des fontes.

http://romain-menard.fr/cours/web/mooc/premiere-etape-changer-le-fond-de-page/

Publié dans tech notes | Tagué , , | Laisser un commentaire