dj3c1t.com

Programmes pour le web

Comms - un proxy modulaire en Java

présentation

Comms est un petit proxy HTTP, écrit en Java. Le proxy se lance en ligne de commande. Une fois démarré, il peut alors servir de relais pour les requêtes HTTP d'un client (navigateur, wget...) configuré pour passer par lui.

Des paramètres permettent de lancer le proxy en mode "verbeux", pour lui faire afficher différentes informations concernant son activité : les URL des requêtes HTTP qu'il traite, les Headers HTTP des requêtes ou des réponses qu'il fait transiter...

Comms peut aussi accueillir d'autres fonctionnalités, qui peuvent être développées sous forme de modules à intégrer à l'application. (voir les modules ci-dessous pour plus d'informations sur le développement de nouvelles fonctionnalités).

pré-requis

Le proxy a été développé en Java a donc besoin d'une machine virtuelle Java pour être éxecuté.

Un compilateur Java est necessaire au développement des modules.

Java est disponible sur http://www.java.com.

version Java

Le proxy a été développé avec la version 1.4.0_02 du sdk de Sun.

scripts

Comms est fourni avec des scripts qui permettent de lancer le proxy, ou de le compiler. Ces scripts partent du principe que les executables necessaires à la compilation Java ("javac" ou "javac.exe") et au lancement de la machine virtuelle Java ("java" ou "java.exe") sont accessibles du dossier d'où ces scripts sont lancés.

Vérifiez, avant d'utiliser ces scripts, que la variable PATH de votre système d'exploitation contient les répertoires appropriés. Ou adpatez les scripts avec vos chemins.

compilation

compil [option]

affiche la liste des options si aucune option n'est précisée.

compil all

pour compiler l'ensemble des classes utilisées par le proxy.

execution

comms [option-flag [option-value] [option-flag [option-value] [...]]]

l'option -h affiche une description des options disponibles et termine le proxy.

Quelques exemples d'utilisation

comms

Lance le proxy en attente de connexions sur localhost:9137.

comms -a 192.168.0.3 -p 8080

Lance le proxy en attente de connexions sur 192.168.0.3:8080.

comms -http

Affiche les requêtes HTTP, au fur et à mesure qu'il les traite.

comms -res-h

Affiche les Headers des réponses HTTP, au fur et à mesure qu'il les traite.

comms -http -req-h -res-h

Affiche plein de trucs.

comms -h

Affiche le détail des options disponibles.

les modules

développement

Une interface Java décrit les méthodes appelées par le proxy lors du traitement d'une requête. Une classe, codable en Java, qui implémente cette interface peut être déclarée comme module au lancement du proxy. Développer un module consiste donc à écrire une classe Java et à implémenter les méthodes de l'interface.

Pour qu'un module puisse fonctionner avec le proxy, il doit implémenter l'interface Module, avec les méthodes suivantes :

public boolean handle(Request request, Connexion CBL)

et

public boolean handle(Response response, Connexion CBL, Connexion ORG)

La première est appelée à chaque fois que le proxy reçoit une nouvelle requête à traiter, et la seconde à chaque réception de réponse d'un serveur sollicité pour l'une de ces réquêtes.

Les instances de Request et Response proposent alors un ensemble de methodes pour acceder aux headers, à l'adresse ip, à l'url... Les instances de Connexion permettent quant à elles d'acceder aux flux en lecture et en écriture sur l'emetteur de la requete (CBL) et l'emetteur de la réponse (ORG). Le booléen retourné indique au proxy s'il doit continuer le traitement de la requete (VRAI) ou si le module s'est chargé de fournir une réponse à l'émetteur de la requete.

Plus de détails sont fournis sur les classes passées en parametre de ces méthodes dans la javadoc.

lancement

comms -m <nom.de.classe>

Une fois que le module a été développé et compilé, il se lance avec le proxy avec l'option -m <nom.de.classe> où <nom.de.classe> désigne le nom, avec le package éventuel, de la classe qui implémente l'interface Module.

note: les classes du module doivent se trouver dans le repertoire classes. C'est dans ce répertoire que le proxy va chercher les classes des modules à lancer.

un example

fichier Trafic.java (disponible dans le dossier src/example):

package example;

import net.comms.Module;
import net.http.Request;
import net.http.Response;
import net.http.Connexion;

public class Trafic implements Module
{
  public boolean handle(Request request, Connexion CBL)
  { System.out.println
    ( "reception d'une requete pour l'url: "
    + request.url()
    + " (ip du demandeur: "
    + request.ip() + ")"
    );
    return true;
  }

  public boolean handle(Response response, Connexion CBL, Connexion ORG)
  { System.out.println
    ( "reception d'une reponse de type: "
    + response.status_code()
    + " suite a la requete pour l'url: "
    + response.request().url()
    + " (ip du serveur: "
    + response.ip() + ")"
    );
    return true;
  }

}

Ce module affiche quelques informations sur les requêtes et les reponses qui transitent par le proxy, et se lance avec la commande:

comms -m example.Trafic

notes & ce qu'il reste à faire

à propos du temps maximum d'attente

Afin d'eviter que le traitement d'une requête ne soit bloqué dans une procédure de lecture/ecriture, la classe IDLE a été codée pour verifier régulièrement qu'une communication est bien active. Si cette dernière est bloquée pendant plus de 30 secondes (temps maximum par defaut), l'instance d'IDLE, lancée par le Standard, termine les connections relatives au traitement de cette communication.

Le temp maximum d'attente est parametrable via l'option -idle <max> où <max> indique ce temps maximum en secondes. Un temps maximum de -1 désactive ce systeme de vérification.

Lorsqu'une communication est terminée pour avoir dépassé le temps d'inactivité autorisé, un message est affiché sur la sortie standard pour indiquer où en était le traitement de la requête au moment de son arrêt. Pour désactiver cet affichage, supprimer la ligne 46 du fichier Comm.java.

à propos du nombre maximum de threads

Le Standard lance un nouveaux thread pour chaque requête à traiter. Par défaut, le nombre maximum de threads tournant simultanément est de 50. Cela signifie qu'au delà de 50 requêtes traitées simultanément, les nouvelles requêtes sont mises en attente.

Un maximum différent peut etre précisé avec l'option -max <max> où <max> indique ce maximum. Un maximum de -1 signifie pas de maximum.

les réponses de type 100 Continue

La RFC2616 décrit un mécanisme de demande de confirmation pour un client qui souhaite faire parvenir à un serveur un corps de requête en étant sûr que ce corp sera lu par le serveur, en joingant dans l'entete de la requête un header Expect: 100-Continue. Dans ce cas, l'éméteur de la requête attendrait que le serveur lui envoie une reponse de type 100 Continue avant de communiquer le corps de la requête. J'ai navigué sur le web pour essayer de trouver un cas où ce mecanisme etait utilisé, mais je n'en ai pas trouvé. Donc à défaut de pouvoir tester un code, j'ai developpé le proxy de manière à ce qu'il renvoie une reponse de type 417 Expectation failed lorsqu'il reçoit une requête avec le header Expect: 100-Continue (cf. Comm.java, ligne 90).

note: afin de pouvoir repérer une requête munie d'un header Expect: 100-Continue, j'ai codé un système d'alerte qui devrait ouvrir, dans un tel cas, une fenêtre indiquant l'url de cette requête. Pour désactiver ce système d'alerte, supprimer la ligne 93 du fichier Comm.java (le fichier Alert.java devient alors inutile).

les connections persistantes

Lorsqu'un navigateur ouvre une page html contenant un grand nombre d'éléments necessitant des requêtes http suplémentaires (images, objets...) la plupart du temps, ces requêtes se font sur le même serveur que sur celui qui fournit la page principale. Pour limiter le nombre de connections, le protocol HTTP/1.1 defini des règles pour prendre en compte une communication faisant intervenir plusieurs requêtes et plusieurs réponses sur une meme connection (dite persistante). Par défaut, les communications en HTTP/1.1 sont considerées comme persistantes. Mais il est possible de spécifier qu'une connection ne doit pas etre considerée comme telle en joignant le header Connection: close. Tel qu'il est codé, le proxy joint ce header à tous ses messages. Cela ne pose pas de problème au niveau du coté operationnel du proxy, puisqu'il décline cette possibilité dans les règles, mais on peut envisager de le rendre plus performant...