Proxy HTTP en Perl

Le but de ce TP est de programmer en Perl un proxy HTTP.

Fonctionnement d'un proxy HTTP

Un proxy (aussi appelé serveur mandataire) est un serveur capable de relayer des pages WEB entre un client et un serveur HTTP.

Au lieu de se connecter directement au serveur indiqué par l'utilisateur, le client se connecte au proxy. Celui-ci contacte le serveur réel, télécharge la page demandée et la retransmet au client. Vu du côté du client, le proxy agit comme un serveur HTTP. Vu du côté du serveur, le proxy agit comme un client HTTP.

Quand le client et le serveur sont séparés par un pare-feu qui interdit toute connection directe, l'emploi d'un proxy (qui tourne sur le pare-feu) est indispensable.

Généralement, un proxy ne se contente pas de relayer de façon transparent toutes les requêtes et les réponses HTTP. Parmi les utilisations possibles d'un proxy HTTP on compte:

Protocole de proxy

Le protocole HTTP supporte explicitement l'utilisation de proxys grâce à des en-têtes dédiées. Une requête d'un client à un proxy ressemble à ceci:

GET http://www.di.ens.fr/~mine/enseignement/syst2006/index.html HTTP/1.1
Host: www.di.ens.fr
Proxy-connection: keep-alive
autres en-têtes
...
ligne vide

On note les différences suivantes avec une requête classique HTTP 1.1:

On rappelle que les en-têtes sont insensibles à la casse, que les lignes sont toutes terminées par \r\n et que la fin de l'en-tête est indiquée par une ligne vide.

Référence: le protocole HTTP 1.1 est décrit dans la RFC 2616. Voir également les explications du TP précédent.

Exercices

Avant toute chose, modifiez les préférences de votre navigateur préféré pour qu'il utilise comme proxy localhost sur le port 8080. Par exemple:

1) Dans un premier temps, vous programmerez uniquement la partie serveur du proxy HTTP. Votre proxy se contentera de répondre une erreur 403 Forbidden à toutes les requêtes du client, sans chercher à contacter de serveur WEB. Un tel programme commence par créer une socket d'écoute sur le port 8080 (socket, bind, listen). Ensuite, dans une boucle infinie, il attend une nouvelle connection (accept), traite une requête (<>, print) et ferme la connection (close). Alternativement à socket, bind, listen, accept, vous pouvez utiliser l'interface de plus haut niveau (orientée-objet) du module IO::Socket::INET.

Correction) proxy1.pl.

2) Ajoutez maintenant la partie client afin de réaliser un proxy HTTP transparent. Celui-ci retransmet chaque requête au serveur indiqué dans l'en-tête Host et renvoie la réponse au client. Testez votre proxy avec plusieurs clients HTTP (Firefox, Konqueror, Lynx, Links, wget, etc). Pensez à bien gérer le cas où le serveur demandé par le client n'est pas joignable (502 Bad Gateway). Pensez également à forcer le mode Connection: close afin que le serveur ferme la socket à la fin de la réponse.

Correction) proxy2.pl.

Notez qu'un proxy réaliste devrait pouvoir traiter plusieurs connections en parallèle (par exemple, à l'aide de select, de fork ou des threads). Ceci sera le sujet de notre prochain TP.

Les question suivantes sont indépendantes et peuvent être traitées dans l'ordre de votre choix.

Gestion des connections keep-alive

Par défaut, si ni le client ni le serveur ne précise l'en-tête Connection: close (ou Proxy-connection: close pour une connection client vers proxy), le serveur (ou proxy) ne fermera pas la connection TCP après avoir répondu à la requête du client. Ceci permet au client de réutiliser la connection pour envoyer plusieurs requêtes, ce qui augmente l'efficacité du protocole. L'efficacité est encore augmentée si le client effectue un tir groupé (pipelining) de plusieurs requêtes avant de lire les réponses: cela permet au serveur de commencer à traiter la deuxième requête alors que la première réponse est en cours de transmission.

Il existe deux conventions pour indiquer au client (ou proxy) la fin d'une réponse à une requête:

3) Modifiez votre proxy pour qu'il gère les connections keep-alive.

Correction) proxy3.pl.

Proxy filtrant

4) Modifiez le proxy de la question 2 pour qu'il ne télécharge pas les images. À la place, le proxy pourra envoyer au client la chaîne suivante, correspondant à une image PNG transparente de taille 1x1:

"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52
\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4
\x89\x00\x00\x00\x0D\x49\x44\x41\x54\x08\xD7\x63\x60\x60\x60\x60
\x00\x00\x00\x05\x00\x01\x5E\xF3\x2A\x3A\x00\x00\x00\x00\x49\x45
\x4E\x44\xAE\x42\x60\x82"

Pour reconnaître les images, on pourra se baser sur l'extension de l'URL ou bien examiner l'en-tête Content-type donnée par le serveur. De plus, on pourra filtrer les URLs de façon à ne bloquer que les images provenant de sites de publicité. Pour cela, on lira dans un fichier de configuration .ads une expression régulière indiquant les URLs à filtrer (e.g.: (.*adserver.*)|(/ad\..*)|(/ads\..*)).

Correction) proxy4.pl.

Proxy cache

5) Ajoutez au proxy de la question 2 un cache qui garde en mémoire les pages WEB envoyées. Si la page est à nouveau demandée, le proxy renverra la copie qu'il a en cache au lieu de se connecter au serveur. Vous pourrez implanter le cache par une simple table de hachage en mémoire vive qui associe à une URL son contenu.

Vous prendrez soin à gérer les en-têtes Cache-control générées par le client ou le serveur. En particulier, un serveur qui ne souhaite pas que sa réponse soit stockée en cache précisera l'option no-cache, no-store ou must-revalidate (e.g.: la page est dynamique). Un client utilisera l'option max-age=0 ou no-cache pour forcer le proxy à redemander au serveur une nouvelle version de la page, même si une version antérieure se trouve en cache (e.g.: l'utilisateur clique sur l'icône actualiser).

Correction) proxy5.pl.

Notes: Un cache plus complet tiendrait compte des en-têtes Expire, Date ainsi que des options max-age et min-fresh et max-stale de Cache-control pour déterminer si une entrée de cache peut être utilisée ou bien doit être rafraîchie. De plus, une implantation plus sophistiquée ferait intervenir un cache sur disque. Un exemple de proxy-cache HTTP couramment utilisé est le logiciel Squid.


Antoine Miné