1.2 Boucle de traitement des requêtes

publicité
JAVA - TP 2
Serveur HTTP
Objectif du TP
L'objectif de ce TP est d'écrire un serveur HTTP multi-thread. Pour cela, vous devez :

suivre la décomposition proposée ;

implémenter les classes demandées ;

mettre des commentaires pour la génération de la documentation avec javadoc ;

utiliser un makefile pour compiler, générer la documentation et lancer le serveur.
Vous placerez l'ensemble de vos classes dans un package httpd.
A travers ce TP, vous allez mettre en oeuvre différentes classes des packages :

java.lang

java.util

java.io

java.net
1. Structure générale du serveur
Le programme se composera pour l'instant de deux classes :

httpd.Main

httpd.Httpd
: la classe contenant la méthode main(). Cette classe sert à lire le fichier de
configuration et à demarrer le serveur.
: la classe qui implémente le serveur HTTP.
Le serveur se lance par :
java httpd.Main config
1.1 Configuration du serveur
L'ensemble des parametres de configuration du serveur est contenu dans un fichier texte qui est
passé en paramétre au programme. Pour lire et exploiter le contenu de ce fichier, on utilisera la
classe java.util.Properties (voir la méthode load()). Ce fichier contiendra, entre autre, le numéro
du port d'écoute du serveur (champs Port) et le répertoire racine des documents web (champs
WebRoot).
Exemple de fichier :
# Numero de port du serveur
Port=8080
# Repertoire racine des documents web
WebRoot=www
1.2 Boucle de traitement des requêtes
Le protocole HTTP est un protocole sans état basé sur TCP. L'algorithme du serveur est le
suivant :
Ouvrir une socket sur le port indiqué
Boucle infinie
Attendre une requête
Récuperer la socket de travail
Lire le contenu de la requête
Analyser la requête
Construire la réponse adaptée
Renvoyer la réponse
Fermer la socket
Fin de boucle
Pour ouvrir la socket d'écoute du serveur, on utilisera la classe java.net.ServerSocket. L'attente
de requête se fait par la méthode accept() qui retourne à chaque nouvelle requête une socket de
travail (instance de la classe java.net.Socket). Pour récupérer le contenu de cette socket, il suffit
de lire sur le flux retourné par getInputStream()
1.3 Traitement d'une requête
Pour l'instant, le serveur ne traitera que les requêtes GET. Ces requêtes sont de la forme :
GET /ressource HTTP/1.0\n\n
Exemple de requête :
GET /rep1/index.html HTTP/1.0\n\n
Cette requête demande à obtenir le fichier index.html situé dans le répertoire rep1, lui-meme
situé dans le répertoire racine des documents Web.
Le serveur lit la requête (classe java.io.DataInputStream méthode readLine()), l'analyse (classe
java.util.StringTokenizer) et renvoit une réponse (classe java.io.PrintStream) de la forme :
Header\n
\n
Body
La partie Header se compose de plusieurs champs contenant des informations utiles pour le
programme client (status de la réponse, date, nom du serveur, type du document retourné, ...) La
partie Body contient soit le document si la requête a pu etre correctement traité, soit un message
d'erreur pour l'utilisateur.
Si la ressource est accessible, la réponse renvoyé est de la forme :
HTTP/1.0 200 OK
Date: Wed, 22 Oct 1997 21:49:50 GMT
Server: JavaHttp/1.0
Content-type: text/html
le contenu du fichier ...
Si la requête est mal formée, la réponse renvoyée est de la forme :
HTTP/1.0 400 Bad Request
Date: Wed, 22 Oct 1997 21:39:48 GMT
Server: JavaHttpd/1.0
Content-type: text/html
<HEAD><TITLE>Mauvaise requête</TITLE></HEAD>
<BODY><H1>Mauvaise requête</H1>
Votre browser a envoyé une requête que ce serveur ne peut pas traiter.<P>
</BODY>
Si la requête est bien formée, le serveur verifie que la ressource est bien accessible à partir du
repertoire racine précisé dans le fichier de configuration (champs WebRoot). Si ce n'est pas le cas,
la réponse renvoyée est de la forme :
HTTP/1.0 404 Not found
Date: Wed, 22 Oct 1997 21:41:59 GMT
Server: JavaHttpd/1.0
Content-type: text/html
<HEAD><TITLE>Fichier non trouvé</TITLE></HEAD>
<BODY><H1>Fichier non trouvé</H1>
La ressource /rep1/une_ressource n'est pas présente sur ce serveur.<P>
</BODY>
2. Typage des ressources
Dans cette première version du serveur, on suppose que les documents renvoyés sont tous du
type HTML (ce qui est indiqué dans les headers des réponses par Content-type: text/html).
Or les documents peuvent être de nature variée (images GIF, JPEG, texte non-formaté, vidéo
AVI, ...). C'est au serveur HTTP d'indiquer le type du document renvoyé. Pour cela, il se base sur
l'extension du nom du fichier pour établier le mime type correspondant. Les mime type sont des
chaînes de caractères de la forme :
type/sous-type
Exemples :
Mime type
Type
text/html
Fichier HTML
text/plain
Fichier texte
image/gif
Image GIF
image/jpeg
Image JPEG
Modifiez le serveur pour qu'il effectue le typage des ressources. Pour cela, on utilisera le fichier
mimetypes (lisible comme le fichier de configuration par java.io.Properties.load())
contenant la liste des extensions possibles et les mime type associés. Le nom de ce fichier sera
indiqué dans le fichier de configuration (champ MimeTypeFile). Si l'extension du fichier
demandé n'est pas connue, le serveur renverra un mime type par défaut (e.g.: text/plain)
indiqué dans le fichier de configuration (champ DefaultMimeType).
3. Traitement multi-thread
Actuellement, le serveur ne peut traiter qu'une requête à la fois. Pour pourvoir traiter plusieurs
requêtes simultanément, nous allons créer des objets capables de prendre en charge les requêtes
et qui s'executeront dans différentes threads . Ces objets seront des instances d'une nouvelle
classe : httpd.HttpConnection. Pour que ces instances soient threadables, cette classe doit
implémenter l'interface java.lang.Runnable. Une grande partie du code de la classe Httpd sera
delocalisée dans cette nouvelle classes (au sein de la méthode run()).
La boucle de traitement des requêtes (située dans la classe Httpd) devient alors :
Ouvrir une socket sur le port indiqué
Boucle infinie
Attendre une requête
Récuperer la socket de travail
Créer une instance de HttpConnection avec en parametre la socket et la
configuration
Fin de boucle
La méthode run() de la classe HttpConnection contient le reste du traitement :
Lire le contenu de la requête
Analyser la requête
Construire la réponse adaptée
Renvoyer la réponse
Fermer la socket
4. Journalisation des accés
On souhaite pouvoir suivre l'ensemble des requêtes auquelles repond notre serveur. Pour cela, on
enregistre chaque requête traitée dans un fichier de journalisation. Les enregistrement sont de la
forme :
[adresse IP du client] [date] [requête] [status de la reponse]
Exemple :
[127.0.0.1] [Sat Mar 08 14:51:47] [GET /icons/dir.gif HTTP/1.0] [200]
[128.93.7.60] [Sat Mar 08 14:53:44] [GET /docs/index.html HTTP/1.0] [404]
Ecrivez une classe httpd.HttpLog de la forme :
package httpd;
class HttpLog {
HttpLog(String logFile) {...}
add(String address, String request, int status) {...}
}
Cette classe sera instanciée avant le démarrage du serveur (dans la classe Httpd) avec le nom du
fichier de journalisation (indiqué dans le fichier de configuration dans le champ LogFile) et sera
fournie à chaque instance de HttpConnection pour qu'elles puissent y ajouter leur requête une
fois celle-ci traitée.
Remarque : le serveur étant multi-thread, assurez vous que l'ajout d'une entrée (méthode add)
soit exclusif.
5. Gestion des repertoires
5.1 Renvoi d'un fichier d'index
Lorsqu'une requête demande une ressource correspondant à un repertoire, il est devenu
conventionel de renvoyé, s'il est présent, un fichier particulier (traditionnelement nomé
index.html). Par exemple la requête :
GET /rep1 HTTP/1.0\n\n
renverra le fichier index.html contenu dans le repertoire rep1. Modifier la classe
HttpConnection pour prendre en compte cette fonctionnalité (le nom du fichier d'index sera
indiqué dans le fichier de configuration dans le champ DirectoryIndex). Pour tester si une
ressource est un répertoire, utilisez la méthode isDirectory() de la classe java.io.File.
5.2 Renvoi du contenu du répertoire
Il est aussi devenu traditionnel que si ce fichier d'index n'existe pas on retourne une page HTML
représentant le contenu du répertoire. Cette page indique généralement la nature des fichiers (au
moyen d'une icone), le nom du fichier (avec un lien pour y acceder) et diverses informations
(taille du fichier, date, ...). Modifiez la classe HttpConnection pour prendre en compte cette
fonctionnalité.
Exemple de page représentant le contenu d'un repertoire :
Téléchargement