TD3 de Java Avancé. Thread et multiThreading Un serveur HTTP L'objectif de ce TD est d'écrire un serveur HTTP multi-thread. Pour cela, vous devez : · suivre la décomposition proposée ; · implémenter les classes demandées ; Vous placerez l'ensemble de vos classes dans un package httpd. A travers ce TD, vous allez mettre en œuvre 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 : la classe contenant la méthode main(). Cette classe sert à lire le fichier de configuration et à démarrer le serveur. · httpd.Httpd : la classe qui implémente le serveur HTTP. Le serveur se lancera par la commande : java httpd.Main config.properties 1.1 Configuration du serveur L'ensemble des paramètres 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 : # Numéro de port du serveur Port=8080 # Répertoire racine des documents web WebRoot=c:/www 1/5 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écupérer 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 exemple de requête http réelle de type GET http://www.perdu.com/ GET / HTTP/1.1 Host: www.perdu.com User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/43.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Connection: keep-alive Pour notre td, le serveur ne traitera que les requêtes GET. Ces requêtes sont de la forme : GET /ressource HTTP/1.1\n Exemple de requête : GET /rep1/index.html HTTP/1.1\n Cette requête demande à obtenir le fichier index.html situé dans le répertoire rep1, lui-même situé dans le répertoire racine des documents Web. Exemple de réponse http réelle HTTP/1.1 200 OK Date: Thu, 13 Jan 2016 05:54:23 GMT Server: Apache Accept-Ranges: bytes X-Mod-Pagespeed: 1.6.29.7-3566 Vary: Accept-Encoding Content-Encoding: gzip Cache-Control: max-age=0, no-cache Content-Length: 1243 2/5 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/html <html><head><title>Vous Etes Perdu ?</title></head><body><h1>Perdu sur l'Internet ?</h1><h2>Pas de panique, on va vous aider</h2><strong><pre> * <----- vous &ecirc;tes ici</pre></strong>//<!//]]></script></body></html> Le serveur lit la requête (classe java.io.BufferedReader méthode readLine()), l'analyse (classe java.util.Scanner) et renvoi 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 (statut 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 être correctement traitée, soit un message d'erreur pour l'utilisateur. Si la ressource est accessible, la réponse renvoyée est de la forme : HTTP/1.1 200 OK Date: Wed, 13 Jan 2016 21:49:50 GMT Server: JavaHttp/1.0 Content-lenght : 1280 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, 13 Jan 2016 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 vérifie que la ressource est bien accessible à partir du répertoire 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, 13 Jan 2016 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> 3/5 2. 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'exécuteront 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 ou étendre la classe java.lang.Thread. Une grande partie du code de la classe Httpd sera délocalisé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écupérer la socket de travail Créer une instance de HttpConnection avec en paramètre 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 3. Journalisation des accès On souhaite pouvoir suivre l'ensemble des requêtes auxquelles répond notre serveur. Pour cela, on enregistre chaque requête traitée dans un fichier de journalisation. Les enregistrements sont de la forme : [adresse IP du client] [date] [requête] [statut de la réponse] 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] Écrivez 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 4/5 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/5