Fichiers statiques et le cache de Apache

Contexte

Depuis des années, je suis embêté par gestion du cache du navigateur sur les fichiers statiques servis par le serveur HTTP Apache.

Encore une fois, ce matin, ce problème de cache me pose problème : alors que j'avais publié une mise à jour sur la page d'actualité du site Coworking Metz, je constate qu'un certain nombre de personnes ne voient pas la mise à jour, car pour la voir il faut vider le cache du navigateur en faisant par exemple un CTRL-F5 sur la page.

J'ai donc décidé d'en savoir plus… pourquoi la configuration par défaut de Apache gère "mal" à mes yeux le cache.

Le comportement que je souhaite avoir est celui-ci :

« Je souhaite que dès que je modifie le contenu d'un fichier statique, celui-ci soit tout de suite visible par mes visiteurs »

Analyse

Lors de mes développements web, j'ai déjà "joué" avec les champs header Cache-Control, If-Modified-Since, ETag, Expires, Last-Modified

Je me suis dit « Apache doit envoyer un champ Expires avec une valeur par défaut ».

J'ai cherché dans la documentation de Apache et je n'ai rien trouvé au sujet de Expires en dehors du module Expires qui n'était pas activé sur mon serveur.

J'ai ensuite cherché à analyser les champs HTTP Response retournés par le serveur sur le site Coworking Metz.

Dans un premier temps, j'ai regardé du coté de Firebug mais je n'ai pas trouvé comment voir le résultat de la requête HTTP de la page courante (dans Network on voit uniquement les ressources de la page).

J'ai ensuite trouvé l'extension HttpFox (que l'on active via le menu Affichage => HttpFox).

Avec cette extension, j'ai pu voir que le serveur Apache retourne les champs Header suivants :

(Status-Line)        HTTP/1.1 304 Not Modified
Date                 Sat, 04 Jan 2014 11:19:29 GMT
Server               Apache/2.2.22 (Debian)
Connection           Keep-Alive
Keep-Alive           timeout=5, max=100
Etag                 "1920d20-442b-4ef22c61a1396"
Vary                 Accept-Encoding

Contrairement à ce que je pensais, par défaut Apache ne retourne pas le champ Expires.

C'est donc Firefox qui a une valeur par défaut et cette valeur par défaut est expliqué ici : How are expiration times calculated (since not every response includes an Expires header) ?.

Le texte suivant mérite un exemple pour être plus facilement compris :

Finally, if neither header is present, then we look for a "Last-Modified" header. If this header is present, then the cache's freshness lifetime is equal to the value of the "Date" header minus the value of the "Last-modified" header divided by 10. This is the simplified heuristic algorithm suggested in RFC 2616 section 13.2.4.

Exemple :

  • la dernière fois que le fichier index.html a été modifié : 2014-01-04 à 11h00 (last_modified_time)
  • le 2014-01-04 à 12h00 le navigateur fait une première requête (date_value)
age_expiration = (date_value - last_modified_time) * 0.1 => 3600 secondes * 0.1 => 360s
  • le 2014-01-04 à 12h01 le navagiteur fait une seconde requête, utilisation du cache car 60s < 360s
  • le 2014-01-04 à 12h10 le navigateur fait une troisième requête, le cache n'est pas utilisé car : 600s > 360s

Conclusion pour atteindre mon objectif, mes requêtes HTTP doivent retourner Expires: 0.

Solution : configuration de Apache

Activation du module Expires :

# a2enmod expires
# service apache2 restart

Placer un fichier .htaccess à la racine du site, avec le contenu suivant :

<IfModule mod_expires.c>
ExpiresActive on
ExpiresDefault "access plus 0 seconds"
</IfModule>

Code HTTP 304

Avec cette configuration, l'activité sur le serveur est certe plus élevée… mais un cache est toujours présent.

Lorsque le navigateur envoie la requête HTTP avec les paramètres suivants :

(Request-Line)      GET /news.html HTTP/1.1
Host                coworking.a-metz.info
Accept-Language     fr,en;q=0.8,fr-fr;q=0.5,en-us;q=0.3
Accept-Encoding     gzip, deflate
Connection          keep-alive
If-Modified-Since   Sat, 04 Jan 2014 11:33:53 GMT
If-None-Match       "1920d20-442d-4ef2366c0e11a"

en fonction de la date de modification du fichier news.html et de la valeur du champ If-Modified-Since, le serveur va renvoyer une réponse avec un Status 200 ou 304.

Dans le cas d'une réponse 304, le contenu de la page n'est pas envoyé au navigateur, c'est le cache du navigateur qui est utilisé.

Conclusion, avec des réponses 304, l'activité du serveur est certe supérieur mais consommation de la bande passante est tout de même limitée car le contenu des pages, des images… n'est pas transmis.

Qu'en est-t-il de Nginx ?

Par défaut, Nginx ne renvoie pas de champ HTTP ETag, ni de champ Expires.

J'ai observé que quand le serveur HTTP ne retourne pas de champ ETag alors le navigateur va interroger le serveur avec un champ HTTP If-Modified-Since pour savoir si il faut ou non utiliser le cache du navigateur.

Conclusion, la configuration par défaut de Nginx correspond au comportement que je souhaite, celui indiqué en début de billet.

Read and Post Comments

Solution simple d'hébergement d'application web Python ?

Je rêve de quelque chose comme Heroku.

Qu'est ce que « Heroku » ?

C'est une solution rapide de déploiement d'applications webs basées sur le langage Ruby et plus précisément Ruby On Rails.

Voici un extrait de la présentation visible sur la page d'accueil du projet.

  1. Création d'une instance sur le serveur d'hébergement :

    $ sudo gem install heroku
    $ heroku create sushi
    Created http://sushi.heroku.com/
    git@heroku.com:sushi.git
    
    L'instance est créée sur le serveur, c'est ultra simple non ?
    Une seule commande pour faire cela : heroku create sushi.
  2. Déploiement de votre application (le tout basé sur git) :

    $ git push heroku master
    -----> Heroku receiving push
    -----> Rails app detected
    -----> Launching..... done
           http://sushi.heroku.com deployed
    

    Un simple push permet de mettre à jour votre site web.

  3. Autres commandes de configuration :

    $ heroku sharing: add fugu@sushi.com
    $ heroku domains: add sushi.com
    $ heroku rake db:migrate
    $ heroku db:push
    $ heroku addons:add memcahed
    

    Ici l'on peut voir comment il est facile d'ajouter un utilisateur, un domaine…

Défaut de "heroku" ?

À mes yeux heroku a un défaut rédibitoire : la plateforme n'est pas libre. Je ne peux pas l'installer sur mes serveurs. Pour utiliser heroku il est obligatoire de passer par leur plateforme l'hébergement.

Cependant, je vois heroku comme un modèle à suivre.

Est-ce que cela existe en Python ?

Un exemple à suivre ? en effet et plusieurs solutions dans le monde Python ont vu le jour :

  • djangy pour du déploiement Django, vraiment semblable à heroku
  • ep.io pour du déploiement WSGI (« We take your Django, Flask, Pylons or other WSGI code »)
  • Gondor pour du déploement Django

Là aussi le problème est que ces solutions ne sont pas libres.

Une solution ouverte ?

Oui, quelque chose d'ouvert existe, mais je ne l'ai pas encore testé : Silver Lining.

Je vous invite à lire la documentation, la solution est totalement libre. C'est un système basé sur Ubuntu (j'aurais aimé Debian).

Faudrait vraiment que j'expérimente cela.

Read and Post Comments

Mesures de bande passante de mon réseau local

Depuis un certain temps, je trouvais mon réseau Wifi très lent. La dernière fois que j'ai dû transférer 20 Mo entre deux portables, cela a pris 20 minutes !

Cette après midi, j'ai décidé d'étudier un peu plus précisément ce problème.

1. Utilisation de "netperf"

netperf est un outil de mesure de performances réseaux.

D'après la documentation, il permet de mesurer de nombreuses choses… pour ma part, je n'ai utilisé que les mesures TCP_STREAM et TCP_MAERTS.

netperf est disponible sous Debian (dans la section non-free) et sous Ubuntu.

Une fois le paquet installé, netserver est lancé automatiquement et écoute sur le port 12865.

Voici un exemple de ce que j'ai fait :

  • j'ai installé le paquet netserver sur la machine A et B
  • j'ai exécuté sur l'une des deux machines la commande suivante :
$ netperf -H 192.168.1.10 -t TCP_STREAM
TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.1.10 (192.168.1.10) port 0 AF_INET : demo
Recv   Send    Send
Socket Socket  Message  Elapsed
Size   Size    Size     Time     Throughput
bytes  bytes   bytes    secs.    10^6bits/sec

 87380  16384  16384    11.36       1.69

2. Mes mesures

J'ai donc utilisé netperf pour mesurer la bande passante mon réseau local dans un peu près toutes les configurations :

Source Destination Débit
Wifi / Ethernet Lieu Wifi / Ethernet Lieu Mbit/s Mo/s
Wifi (eeepc) Bureau Wifi (macbook) Bureau 0,01 0,00125
Wifi (eeepc) Proche de la freebox Wifi (macbook) Proche de la freebox 1,16 0,15
Wifi (macbook) Proche de la freebox Ethernet (PC fixe) - 6,36 0,795
Wifi (eeepc) Proche de la freebox Ethernet (PC fixe) - 1.52 0,19
Ethernet (eeepc) - Ethernet (PC fixe) - 94,03 11,75
Ethernet (macbook) - Ethernet (PC fixe) - 93.96 11,75

Pour avoir un ordre de grandeur, voici les valeurs maximales théorique de mon réseau :

  • Ethernet : 100 Mbit/s soit 12,5 Mo/s
  • Wifi IEEE 802.11g : 54 Mbit/s théoriques, 25 Mbit/s réels soit 3,125 Mo/s

On peut constater que le débit Wifi de mon réseau est catastrophique ! D'une incroyable lenteur entre wifi à wifi : 1,25 Ko/s !

Par contre, pas de problème au niveau du réseau Ethernet, je suis très proche des valeurs maximales de transfert.

Pour améliorer le wifi, j'ai essayé de changer de canal, étant donné qu'il y a de nombreux réseaux wifi dans ma zone.
Sur ma Freebox, la sélection du canal était activée en mode automatique :
« Si vous activez le choix automatique du canal, la Freebox choisira elle même, à chaque démarrage, le canal Wifi le moins perturbé. Cela vous permet d'obtenir une connexion plus fiable. »

J'ai choisi de le passer en manuel et de choisir le canal 2 après avoir vérifié avec wifi-radar que personne ne l'utilise dans ma zone. Après observation, je constate que tous les utilisateurs de Neuf sont sur le canal 11… et il y a du monde ! Je pense que leurs débits doivent être catastrophique.

Voici mes mesures après être passé sur le canal 2 :

Source Destination Débit
Wifi / Ethernet Lieu Wifi / Ethernet Lieu Mbit/s Mo/s
Wifi (eeepc) Bureau Wifi (macbook) Bureau 7,36 0,92
Wifi (eeepc) Proche de la freebox Wifi (macbook) Proche de la freebox 7,78 0.97
Wifi (macbook) Proche de la freebox Ethernet (PC fixe) - 20,66 2.58
Wifi (eeepc) Proche de la freebox Ethernet (PC fixe) - 22,41 2,8

Les résultats sont vraiment meilleurs, je suis presque aux valeurs maximales du Wifi.

Voici maintenant des mesures vers l'extérieur : un serveur situé chez un hébergeur.

Source Destination Débit
Wifi / Ethernet Lieu Wifi / Ethernet Lieu Mbit/s Mo/s
Ethernet (PC fixe) À mon domicile via ADSL Hébergeur 0.87 0,108
Ethernet (PC fixe) À mon lieu de travail via ADSL Hébergeur 0.84 0,105
via ADSL Hébergeur Ethernet (PC fixe) À mon domicile 5.49 0,686
Ethernet (Serveur A) Hébergeur A Ethernet (Serveur B) Hébergeur A 94.13 11,76

Voici les caratéristiques de ma ligne ADSL :

  • Votre ligne est raccordée à un DSLAM compatible ADSL2+
  • Longueur : 2159 mètres
  • Affaiblissement : 32 dB

Voici les caratéristiques de la ligne ADSL à mon lieu de travail :

  • Votre ligne est raccordée à un DSLAM compatible ADSL2+
  • Longueur : 1490 mètres
  • Affaiblissement : 22 dB

La bande passante maximale pour une connexion ADSL2+ :

  • en réception : 28 Mbit/s théoriques soit 16 Mbit/s en pratique
  • en émission : 1,2 Mbit/s théoriques

Informations supplémentaires

Read and Post Comments

Mes flux :