Patrice Lamarque Projet de réseau
Maîtrise info. 2001, UMLV Serveur Proxy en Java


Présentation

 

 

Définition du Proxy (extrait de l'énoncé)

 

L'objectif de ce projet est d'écrire un relais (proxy) filtrant HTTP/1.1 concurrent.
Un relais HTTP relaye les requêtes HTTP reçues d'un client vers le serveur demandé. En fait, au lieu de s'adresser directement au serveur contenant un document, le client fait la requête au relais, le relais relaye cette requête vers le serveur concerné ou un autre relais, il reçoit la réponse (le document) et le retourne au client. Le protocole de communication entre le client et le relais est HTTP, le seul impératif est que la ligne de requête (GET) HTTP contienne l'URL complet (en particulier, le nom du serveur auquel le relais doit s'adresser)

 

 

A propos...

 

Le projet

Le projet a été réalisé par Patrice Lamarque dans le cadre d'un projet de maîtrise à l'UMLV.

Ce programme est un proxy HTTP/1.1 entièrement écrit en java. Il utilise Java 2 et a été écrit à l'aide du JDK 1.3 de Sun. Il a été développé partiellement en environnement Unix et partiellement en environnement Windows. Le programme a été testé (avec plus ou moins de succès) sur les navigateurs Internet Explorer (v5.01 et v5.5), Netscape Navigator (v4.71) et Mozilla (v0.9).

 

Archive

L'archive fournie est au format tar.gz. La racine de l'archive contient ce fichier (rapport.html) ainsi que 3 répertoires :

 
  • doc contient la documentation javadoc
  • class contient les fichiers compilés avecjavac
  • src contient le code source du programme

 

Fonctionnalités

 

Dans l'énoncé du projet, il est spécifié que certaines fonctionnalités sont à implémenter en priorité et que certaines sont optionnelles. La plupart des fonctionnalités de base ont été implémentées et validées, certaines par contre on été implémentées mais ne disposent pas de la fiabilité nécessaire, enfin certaines n'ont pas du tout été implémentées.

 


Fonctionnalités validées

  • Ecoute sur plusieurs ports TCP
  • Filtrage entrant et sortant basé sur nom de machine
  • Gestion d'un pool de connexions client
  • Gestion d'un pool de connexions serveur
  • Configuration centralisée dans un fichier
  • Journalisation des requêtes
  • Journalisation des erreurs du Proxy

Fonctionnalités non validées

  • Gestion découplée du Keep-Alive
  • Configuration à distance et à la volée du relais via HTTP.


Fonctionnalités non implémentées

  • Gestion des méthodes CONNECT, PUT, DELETE, TRACE
  • Gestion des droits de connexion au relais via HTTP Basic.
  • Supervision à distance via HTTP.
  • Configuration et la supervision du relais en utilisant SNMP.
  • Gestion de cache


 


La gestion des flux

 

 

La gestion des flux est d'une importance critique dans une application réseau comme celle-ci. Le langage java fournit une API riche pour la gestion des flux, permettant de manipuler des objets de haut niveau. Cependant, cette dernière est complexe et manque de performance.

Note importante Dans une version précédente, la lecture des entêtes HTTP était réalisée à l'aide d'un objet bufferisé. Ce qui posait de nombreux problèmes. Ce choix avait été fait pour éviter d'utiliser une méthode deprecated de l'API. J'ai du revenir à la version deprecated pour éviter ces problèmes. Cependant, la méthode utilisée pose des problèmes avec la conversion des jeux de caractères. Ainsi, certains navigateurs (comme Mozilla), ne sont pas capables de lire les entêtes renvoyés par le proxy. Ce problème aurait pu être contourné en reécrivant la méthode qui pose des problèmes, mais ceci n'a pas été fait.

 

J'ai décomposé la gestion des flux pour le Proxy en 3 parties : les flux entrants, les flux sortants et les entêtes HTTP.


Les entêtes HTTP (HTTPHeader)

La classe HTTPHeader dérive de la classe Header. Cette dernière permet de lire sur un flux, des lignes contenant des paires clé-valeur. En interne, elle est implémentée par une table de hachages ce qui permet de fournir des méthodes simples de manipulation et de lecture.
HTTPHeader fournit une couche supplémentaire permettant d'interpréter les champs au sens HTTP.


Les flux entrants (HTTPInputStream)

La classe HTTPInputStream hérite de la classe DataInputStream qui vient avec le JDK.
Elle a pour responsabilité la lecture des flux HTTP provenant du client comme du serveur. Une classe interne (ChunkedHTTPInputStream) permet de lire un flux 'morcelé' (chunked dans la RFC 2616).

Les flux Sortant (HTTPOutputStream)

La classe HTTPOutputStream hérite de la classe DataOutputStream qui vient avec le JDK.
Elle a pour responsabilité de relayer les réponses HTTP provenant des serveurs vers les clients.

 


---Schéma des flux sur le Proxy---


La gestion des connexions

Second point critique, la gestion des connexions sur un Proxy va déterminer ses performances. Un proxy concurrent doit être capable de supporter une charge importante de connexions clientes et serveur simultanées. Pour faire face à une telle problématique, Java met à notre disposition les processus légers (Threads). J'ai distingué et implémenté différemment la gestion des connexions serveur et la gestion des connexions client.

 


Gestion des connexions client

Le système implémenté fait appel à 3 classes : RestartableThread, Connection et ConnectionPool

 


Des Threads redémarrables (RestartableThread)

Les Connections seront stockées dans un état inactif dans un pool (voir ci-dessous) qui devra les réactiver à la demande. Pour simuler ce phénomène, la classe Connection hérite de la classe RestartableThread. Il s'agit d'une classe héritant elle-même de la classe Thread.
Afin de permettre aux connexions de rester inactives, il faut un mécanisme d'arrêt / reprise sur les Threads. Le mécanisme normal de traitement d'une thread est l'appel de la méthode start() suivi de la méthode run().

Ici, la méthode run() a été écrite pour exécuter une méthode doRun() (qui doit être écrite dans les sous-classes) puis arrêter le processus léger à l'aide d'un moniteur.
La méthode start() dévérouille le moniteur lorsqu'elle est appelée.

Ainsi Le code que doit exécuter un objet RestartableThread doit se trouver dans la méthode doRun(). L'arrêt est automatique après exécution de ce code. Il faut appeler explicitement la méthode start() pour que le estartableThreadsoit réactivé.

 

Les connexions client (Connection)

La classe Connection représente une connexion client, c'est à dire une connexion réalisée par le User-Agent vers le proxy à destination d'un serveur. Elle hérite de RestartableThread et redéfini donc la méthode doRun(). C'est dans cette méthode que devront être définis tous les services du proxy (filtrage, authentification, cache, etc…).

Une Connection dispose d'un flux entrant et d'un flux sortant pour gérer respectivement la requête et la réponse HTTP.
La classe Connection peut demander une connexion serveur (voir plus bas) sur laquelle elle va lire la réponse HTTP renvoyée par le serveur et la recopier sur son flux sortant.
Enfin, c'est cette classe qui a la responsabilté de générer les erreurs proxy (400, 500, 403,…).

 

Le pool de connexions client (ConnectionPool)

Afin d'éviter de créer de nouveaux processus à chaque connexion, il faut un mécanisme permettant de garder une réserve de connexions déjà allouées et prêtes à l'emploi. La classe ConnectionPool offre un tel mécanisme. Définie selon les spécifications de l'énoncé, elle gère à l'aide de deux Vector l'ensemble des connexions libres et utilisées sur le serveur. Le nombre de connexions est borné par les valeurs MAX_THREAD et MIN_THREAD.


Pour pouvoir supporter des montées en charge brusques, il faut prévoir une marge de manœuvre. Pour cela le ConnectionPool lance une thread (à priorité variable) qui va se charger de créer de créer de nouvelles connexions lorsque le nombre de connexions libres descend en dessous d'un seuil défini par la valeur MIN_SPARE_THREAD.
Enfin, toujours dans un souci d'économie de ressources, il ne faut pas garder un nombre trop important de connexions inactives. Ainsi le ConnectionPool détruit les connexions superflues lorsque le nombre de connexions libre atteint la valeur MAX_SPARE_THREAD.

Ces quatres valeurs sont configurables au sein du fichier de configuration général du proxy.

 

Gestion des connexions serveur

Le système fait appel à deux classes ServConnection et ServConnectionManager.

Les connexions serveur (ServConnection)

La classe ServConnection englobe les flux de lecture et d'écriture sur les serveur. Elle garde trace du temps d'inactivité de la connexion afin de permettre au gestionnaire de connexions serveur d'éliminer les connexions trop anciennes.


Le gestionnaire de connexions serveur (ServConnectionManager)

La classe ServConnectionManager, n'est pas à proprement parler un pool de connexion comme celui des connexions client. Il s'agit en fait d'un gestionnaire permettant de maintenir des connexions serveur pendant un temps limité. Ces connexions ne seront pas gardées indéfiniment mais pendant une durée définie par la valeur KEEP_ALIVE_TIME. Une thread se charge de supprimer les connexions serveur trop anciennes.

 

 


---Gestion des connexions---



Les fonctionnalités en détail

 

 

 

Ecoute sur plusieurs ports

Le lanceur (ProxyLauncher) a la responsabilité de charger la configuration et configurer le proxy. Parmi les directives du fichier de configuration on trouve LISTENING_PORTS dans la section PORTS. Une liste de numéros ports (séparés par des blancs) doit être fournie comme valeur.


Le proxy en lui-même est représenté par la classe Proxy. Celle-ci correspond à une instance tournant sur un seul port. Sa tâche consiste à rester à l'écoute et créer les sockets à chaque fois qu'une requête est effectuée, ensuite le relais est passé au ConnectionPool.
Ainsi, le ProxyLauncher lance autant de threads que de ports à écouter.


Filtrage

L'énoncé spécifie que l'on doit pouvoir interdire à des machines clientes le service du proxy, tout comme l'on souhaite interdire l'accès à certains serveurs HTTP. Cette fonctionnalité est réalisée à l'aide de la classe Policy.

Un objet Policy correspond à un ensemble de règles d'acceptation ou de refus de service. Ces règles sont définies dans des fichiers dont le nom est configurable dans le fichier de configuration central. Un exemple de règles est par exemple :

deny pcguest.univ-mlv.fr
allow *.univ-mlv.fr

La politique de filtrage est interrogée à partir de la méthode accept() qui effectue la vérification.
Un flag ACCEPT_NO_MATCH indique si la politique instanciée doit accepter ou refuser pour le cas où aucune règle n'a été définie.
Le proxy fait donc appel à deux Policy, une pour les connexions client (inPolicy)et une pour les connexion serveur (outPolicy).

Configuration

En interne la configuration est stockée sous la forme d'une table de hachages dont les clés sont les noms des sections et les valeurs des objets Properties. Cet objet Properties contient lui les différentes paires clé-valeur de la section.
La classe fournit une interface permettant de manipuler la configuration afin de faciliter le rechargement à la volée des paramètres. Cette fonctionnalité n'a cependant pas été implémentée.


Journalisation

La journalisation est assurée par la classe Logger. Celle-ci permet de journaliser dans deux fichiers séparés, les accès et les erreurs et/ou messages du proxy.


Les accès sont journalisés sous la forme donnée dans l'énoncé :

[pat/192.168.1.4] [Mon Jun 04 23:42:25 CEST 2001] [GET http://sf-images.osdn.com/SourceForge/pc.gif?index,991690943822 HTTP/1.0] [ACCEPTED]
[pat/192.168.1.4] [Mon Jun 04 23:42:25 CEST 2001] [GET http://sourceforge.net/images/steel3.jpg HTTP/1.0] [ACCEPTED]
[pat/192.168.1.4] [Mon Jun 04 23:42:32 CEST 2001] [GET http://freshmeat.net/img/fmII-logo.gif HTTP/1.0] [ACCEPTED]
[pat/192.168.1.4] [Mon Jun 04 23:42:32 CEST 2001] [GET http://freshmeat.net/ HTTP/1.0] [ACCEPTED]
[pat/192.168.1.4] [Mon Jun 04 23:43:36 CEST 2001] [GET http://www.microsoft.com/isapi/redir.dll?prd=ie&pver=5.0&ar=CLinks HTTP/1.0] [403]

 

 

Les messages sont journalisés sous la forme :[TYPE] [DATE] [MESSAGE]. Trois types de messages on été définis : MESSAGE, WARNING, ERROR.

Exemple de log de message:

[MSG] [Mon Jun 04 20:35:05 CEST 2001] [Proxy instance running, listening on port 11007]
[WARNING] [Mon Jun 04 20:35:17 CEST 2001] [Could not properly write to server output stream, will try a new one]

 

Gestion du Keep-Alive

La gestion du Keep-Alive a posé et pose toujours de nombreux problèmes.
En effet avec l'introduction dans HTTP/1.1 du Keep-Alive comme mode par défaut, les proxies doivent faire face à des implémentations diverses du protocole. Peut se trouver dans des situations où il a affaire à deux versions du protocole de chaque côté de la chaine HTTP.


Maintenir une connexion permanente côté serveur alors que le client fonctionne en HTTP/1.0 s'avère un exercice périlleux, de même que le contraire. De plus certains clients HTTP/1.0 implémente tout de même les connexions permanentes ce qui complexifie la chose. Si bien que la RFC recommande de ne pas établir de connexion permanente avec un serveur HTTP/1.1 si le client fonctionne en HTTP/1.0