Programmation Réseaux
TP 1
Implémentation d’un serveur Web en utilisant
les sockets
1. Objectifs du TP
1. Manipuler les Entrées/Sorties en Java
2. Manipuler les processus de communication réseau (Socket)
3. Gestion multitâche des processus
4. Implémenter, de manière minimaliste, une application courante des réseaux (le serveur Web)
2. Mise en place
Le TP s’effectuera sous Linux. Au démarrage lors du multiboot, vous choisirez Linux Réseau. Le
login et mot de passe pour accéder à une session sont elec/elec. Pour programmer en java, vous
pourrez utiliser l’éditeur de votre choix (gedit, emacs…).
Pour compiler et exécuter un programme, il vous suffira d’ouvrir un terminal et de taper les
commandes appropriées en étant positionné dans le bon répertoire.
3. Rappels : Entrée/Sortie en Java
Afin d’interagir le plus efficacement possible avec l’utilisateur, un programme doit être capable de
recevoir des informations provenant de différents périphériques et d’afficher du contenu à l’écran.
La gestion des entrées/sorties permettent d’interagir avec de nombreux éléments du système. Nous
pourrons donc accéder au système de fichier pour pouvoir lire ou écrire un flux de données mais
également lire des données tapées au clavier et afficher du contenu sur l’écran.
3.1. Accéder aux fichiers
Un système se compose essentiellement de fichiers. Les systèmes actuels sont basés en grande
partie sur les fichiers. L’ensemble des composants d’un système peuvent être perçus comme un
fichier et la politique, pour interagir avec, restera donc la même. Afin de bien comprendre le
fonctionnement des entrées/sorties au niveau du système de fichier, nous allons en premier lieu
s’attaquer à la classe File et aux méthodes de lecture et écriture qui y sont rattachées.
3.1.1. La classe File
La classe File représente de façon abstraite un fichier ainsi que les caractéristiques qui y sont
associées comme le nom du fichier et le chemin pour y accéder. Les systèmes actuels ayant une
représentation différente du chemin d’un fichier, cette classe donne une vue indépendante d’un
chemin de la part d’un système à arborescence hiérarchique.
Un chemin est composé de deux éléments permettant d’avoir une représentation indépendante du
système d’exploitation :
1. Une chaine de caractères optionnelle dépendant du système tel que la lettre représentant le
disque dur ou « \\\\ » pour un chemin UNC pour les systèmes windows, « / » pour la racine
d’un système UNIX.
2. Une séquence de zero ou plusieurs noms.
La classe File est composée de quatre constructeurs qui permettent de créer une instance d’un
fichier à partir de :
- Un String représentant le nom du chemin du fichier
- Un File ou un String représentant le répertoire parent, et un String représentant le nom d’un
fichier
- Un URI
La classe File possède également de nombreuses méthodes permettant d’obtenir les caractéristiques
des éléments qui composent le système de fichier. Les méthodes les plus souvent usitées sont les
suivantes :
- public boolean createNewFile() :
Permet de créer un nouveau fichier vide si et seulement si le nom du fichier n’existe pas déjà.
- public boolean isDirectory() :
Teste si le fichier est un répertoire.
- public boolean isFile() :
Teste si le fichier est un fichier normal.
- public long length() :
Retourne la longueur du fichier.
De nombreuses autres méthodes sont disponibles. Il est possible d’avoir une représentation détaillée
des éléments composants la classe File à l’aide de la documentation :
http://docs.oracle.com/javase/1.5.0/docs/api/index.html?java/io/File.html
3.1.2. Lecture d’un fichier
Différentes méthodes nous sont offertes lorsqu’on souhaite lire dans un fichier. En fonction des
besoins qu’on a, certaines méthodes seront plus appropriées que d’autres. Par la suite, quelques
méthodes sont listées en fonction des besoins de l’utilisateur :
- Lecture d’un fichier RAW :
Les fichiers RAW sont des fichiers dont les données sont représentées dans un format brut. Ces
fichiers sont généralement des images, des fichiers de mesures…
1. Classe FileInputStream
Un des constructeurs de la classe FileInputStream est le suivant :
- FileInputStream(File file) :
Permet la création d’un objet FileInputStream en ouvrant une connexion au fichier fourni en
paramètre.
Trois méthodes de lecture sont fournies :
- int read() :
Lit un octet de donnée sur le flux d’entrée.
- int read(byte[] b) :
Lit un ensemble d’octets de longueur b.length sur le flux d’entrée.
- int read(byte[] b, int off, int len) :
Lit len octet sur le flux d’entrée et le copie dans le tableau b en partant de l’indice off.
L’ensemble des fonctions de lecture retourne le nombre d’octets lus, et -1 dans le cas de la fin du
fichier.
Il est important lorsqu’on en a plus besoin de fermer l’accès au fichier. Pour cela, il faut utiliser la
méthode :
- void close() :
Ferme le fichier.
- Lecture d’un fichier en mode texte :
Contrairement aux fichiers RAW, les fichiers en mode texte contiennent un flux de caractères qui le
composent. Les caractères dans un tel fichier suivent le même encodage. Il est donc essentiel de
bien respecter l’encodage lors de la lecture ou de l’écriture afin de décoder correctement le fichier.
1. Classe FileReader
C’est la classe la plus adaptée pour la lecture de fichier contenant des caractères. Elle utilise
l’encodage et une taille de buffer par défaut.
Un des constructeurs de la classe est le suivant :
- FileReader(File file) :
Crée un nouvel objet FileReader en ouvrant une connexion au fichier file.
Les méthodes de lecture et fermeture sont héritées de la classe InputStreamReader. Voir en dessous.
2. Classe InputStreamReader
Un objet de la classe InputStreamReader est un pont réalisant le transcodage entre un flux d’octets
et une chaîne de caractère. En fait, il lit des octets et les décode en caractères en utilisant un
encodage passé en paramètre.
Les constructeurs, les plus utilisés de cette classe, sont :
- InputStreamReader(InputStream in) :
Crée un objet InputStreamReader qui utilise l’encodage par défaut (fonctionnement équivalent à
l’objet FileReader).
- InputStreamReader(InputStream in, Charset cs) :
Crée un objet InputStreamReader qui utilise l’encodage spécifié en paramètres.
Les méthodes de lecture associées à une telle classe sont :
- int read() :
Lit un caractère sur le flux d’entrée.
- int read(char[] cbuf, int offset, int length) :
Lit un ensemble de caractères qui sont copiés dans une portion d’un tableau.
L’ensemble des fonctions de lecture retourne le nombre de caractères lus, et -1 dans le cas de la fin
du fichier.
Il est important lorsqu’on en a plus besoin de fermer l’accès au fichier. Pour cela, il faut utiliser la
méthode :
- void close() :
Ferme le fichier.
3. Classe BufferedReader
Cette classe permet de lire du texte d’un flux de caractères afin d’accroître l’efficacité de la lecture
de caractères ou de lignes et tableaux de caractères.
En général, chaque requête de lecture d’un objet héritant de la classe Reader permet de lire un flux
de caractères ou d’octets. Il est, par conséquent, conseillé d’entourer avec un objet BufferedReader
chaque objet Reader dont les opérations de lecture peuvent être couteuses, telles que pour un
FileReader ou un InputStreamReader.
Ex: BufferedReader in = new BufferedReader(new FileReader("foo.in"));
Dans un tel exemple, l’entrée du fichier spécifié va être bufferisée. Sans l’utilisation d’un buffer,
chaque invocation de la method read() ou readLine() va lire des octets du fichier qui seront
convertis en caractères et retournés, ce qui peut être particulièrement inefficace.
Les constructeurs de cette classe sont :
- BufferedReader(Reader in) :
Crée un buffer, avec une taille par défaut, sur l’entrée d’un flux de caractères.
- BufferedReader(Reader in, int sz) :
Crée un buffer, de taille sz, sur l’entrée d’un flux de caractères.
Les méthodes de lecture associées à une telle classe sont :
- int read() :
Lit un caractère sur le flux d’entrée.
- int read(char[] cbuf, int offset, int length) :
Lit un ensemble de caractères qui sont copiés dans une portion d’un tableau.
- String readLine() :
Lit une ligne de texte.
L’ensemble des fonctions de lecture retourne le nombre de caractères lus, et -1 dans le cas de la fin
du fichier.
Il est important lorsqu’on en a plus besoin de fermer l’accès au fichier. Pour cela, il faut utiliser la
méthode :
- void close() :
Ferme le fichier.
3.1.3. Ecriture dans un fichier
Différentes méthodes nous sont offertes lorsqu’on souhaite écrire dans un fichier. En fonction des
besoins qu’on a, certaines méthodes seront plus appropriées que d’autres. Par la suite, quelques
méthodes sont listées en fonction des besoins de l’utilisateur :
- Ecriture dans un fichier RAW :
Les fichiers RAW sont des fichiers dont les données sont représentées dans un format brut. Ces
fichiers sont généralement des images, des fichiers de mesures…
1. Classe FileOutputStream
Un FileOutputStream est un flux de sortie pour écrire des données dans une instance File. La
disponibilité d’un fichier ou la possibilité de le créer dépendent de la plateforme sous-jacente
utilisée. Certaines plateformes permettent à un fichier d’être ouvert en écriture par seulement une
instance FileOutpoutStream en même-temps. Dans de telles circonstances, le constructeur de cette
classe échouera si le fichier est déjà ouvert.
Deux constructeurs de cette classe sont :
- FileOutputStream(File file) :
Crée une instance permettant d’écrire dans le fichier file.
- FileOutputStream(File file, boolean append) :
Crée une instance permettant d’écrire dans le fichier file. Si le paramètre append est égal à TRUE
alors le flux d’octets est écrit à la fin du fichier.
Trois méthodes de lecture sont fournies :
- void write(int b) :
Ecrit un octet de donnée sur le flux de sortie.
- void write(byte[] b) :
Ecrit un ensemble d’octets de longueur b.length sur le flux de sortie.
- void write(byte[] b, int off, int len) :
Ecrit une portion du tableau b en partant de l’indice off et de longueur len octets sur le flux de
sortie.
Il est important lorsqu’on en a plus besoin de fermer l’accès au fichier. Pour cela, il faut utiliser la
méthode :
- void close() :
Ferme le fichier.
- Ecriture dans un fichier en mode texte :
Contrairement aux fichiers RAW, les fichiers en mode texte contiennent un flux de caractères qui le
composent. Les caractères dans un tel fichier suivent le même encodage. Il est donc essentiel de
bien respecter l’encodage lors de la lecture ou de l’écriture afin de décoder correctement le fichier.
1. Classe FileWriter
C’est la classe la plus adaptée pour l’écriture d’une chaîne de caractères dans un fichier. Elle utilise
l’encodage et une taille de buffer par défaut. La disponibilité d’un fichier ou la possibilité de le
créer dépendent de la plateforme sous-jacente utilisée. Certaines plateformes permettent à un fichier
d’être ouvert en écriture par seulement une instance FileWriter en même-temps. Dans de telles
circonstances, le constructeur de cette classe échouera si le fichier est déjà ouvert.
Deux des constructeurs de la classe sont les suivants :
- FileWriter(File file) :
Crée un nouvel objet FileWriter en ouvrant une connexion au fichier file.
- FileWriter(File file, boolean append) :
Crée un nouvel objet FileWriter en ouvrant une connexion au fichier file et en ajoutant les
caractères écrits en fin de fichier si le paramètre append est égal à TRUE.
Les méthodes d’écriture et fermeture sont héritées de la classe OutputStreamWriter. Voir en
dessous.
2. Classe OutputStreamWriter
Un objet de la classe OutputStreamWriter est un pont réalisant le transcodage entre une chaîne de
caractère et un flux d’octets. En fait avant d’être écrit, les caractères sont encodés en octets en
utilisant un encodage passé en paramètre.
Les constructeurs, les plus utilisés de cette classe, sont :
- OutputStreamWriter(OutputStream out) :
Crée un objet OutputStreamWriter qui utilise l’encodage par défaut (fonctionnement équivalent à
l’objet FileWriter).
- OutputStreamWriter(OutputStream out, Charset cs) :
Crée un objet OutputStreamWriter qui utilise l’encodage spécifié en paramètres.
Les méthodes d’écriture associées à une telle classe sont :
- void write(int c) :
Ecrit un caractère sur le flux de sortie.
- void write(char[] cbuf, int off, int len) :
Ecrit une portion d’un tableau de caractères sur le flux de sortie.
- void write(String str, int off, int len) :
Ecrit une portion d’un String sur le flux de sortie.
Il est important lorsqu’on en a plus besoin de fermer l’accès au fichier. Pour cela, il faut utiliser la
méthode :
- void close() :
Ferme le fichier.
3. Classe PrintWriter
Cette classe permet d’écrire la représentation formatée d’objets dans un flux de sortie. Elle est
particulièrement utilisée à des fins de convenance pour l’utilisateur puisqu’elle permet de
énormément faciliter la gestion de l’écriture de chaines de caractères. Elle permet de vider le flux
automatiquement, lorsque les méthodes println, printf, ou format sont utilisées plutôt que
d’attendre le caractère de fin de ligne ‘\n’ soit écrit. Ces méthodes permettent donc d’utiliser la
propre notion du séparateur de ligne de la plateforme et non plus le caractère de fin de ligne.
Quelques constructeurs intéressants, de cette classe, sont :
- PrintWriter(OutputStream out) :
Crée un objet PrintWriter sans vidage automatique du buffer lors de la réception du séparateur de
ligne.
- PrintWriter(OutputStream out, boolean autoFlush) :
Crée un objet PrintWriter avec vidage automatique du buffer lors de la réception du séparateur de
ligne.
Cette classe comporte de nombreuses fonctions d’écriture, toutes aussi intéressantes les unes que les
autres. On peut citer, pour exemple, les méthodes suivantes mais on se référera à la documentation
pour avoir plus d’informations :
- void print(String s) :
Ecrit un String.
- void println() :
Termine la ligne courante en écrivant le séparateur de ligne.
- void println(String x) :
Ecrit un String et termine la ligne.
Il est important lorsqu’on en a plus besoin de fermer l’accès au fichier. Pour cela, il faut utiliser la
méthode :
- void close() :
Ferme le fichier.
4. Classe BufferedWriter
Cette classe permet d’écrire du texte dans un flux de caractères en sortie en donnant la
fonctionnalité de stocker les caractères afin de rendre plus efficace la fonction d’écriture (optimise
l’écriture de caractères, de tableau de caractères, de String…). Une méthode newLine() est fournie
et permet de rajouter la notion de séparateur de ligne qui est dépendante du système d’exploitation.
En effet, tous les systèmes n’utilisent pas le caractère ‘\n’ pour terminer une ligne.
En général un Writer envoie immédiatement sa sortie au flux d’octets ou de caractères sous-jacent.
A moins qu’une écriture soit immédiatement requise, il est préférable d’entourer n’importe quel
Writer d’un BufferedWriter. En effet, les opérations d’écriture peuvent être couteuses pour certains
Writer tels que FileWriter ou OutputStreamWriter.
Ex : PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out")));
Cet exemple va stocker la sortie de l’objet PrintWriter vers le fichier. Sans stockage, chaque appel à
la méthode print() ferait en sorte de convertir les caractères en octets et de les écrire directement
dans le fichier. Dans de nombreux cas, un tel fonctionnement serait totalement inefficace.
Les deux constructeurs composants cette classe sont :
- BufferedWriter(Writer out) :
Crée un buffer sur le flux de caractères en sortie en utilisant une taille par défaut pour le buffer.
- BufferedWriter(Writer out, int sz) :
Crée un buffer sur le flux de caractères en sortie avec un buffer de taille sz.
Quelques méthodes de cette classe sont :
- void write(int c) :
Ecrit un caractère sur le flux de sortie.
- void write(char[] cbuf, int off, int len) :
Ecrit une portion d’un tableau de caractères sur le flux de sortie.
- void write(String str, int off, int len) :
Ecrit une portion d’un String sur le flux de sortie.
Il est important lorsqu’on en a plus besoin de fermer l’accès au fichier. Pour cela, il faut utiliser la
méthode :
- void close() :
Ferme le fichier.
3.2. Accéder aux périphériques
Pour rendre un programme dynamique, il est important de pouvoir interagir avec l’utilisateur. Pour
cela, divers périphériques sont mis à notre disposition pour réaliser cela, tels que le clavier et
l’écran. Pour réaliser de telles choses en Java, le fonctionnement est extrêmement proche de la
gestion des fichiers (tout est fichier dans un système d’exploitation). Nous allons voir dans cette
partie comment faire en sorte pour récupérer une chaine de caractères provenant du clavier et
afficher du texte sur l’écran.
3.2.1. Lire au clavier
Deux principales méthodes se distinguent en Java pour la lecture au clavier d’une chaine de
caractères.
1. Classe System
La classe System permet d’avoir un accès direct à l’entrée du clavier. Pour cela, on utilisera
l’attribut statique in qui nous est fourni par cette classe. L’attribut in étant de type InputStream, il
sera possible de la combiner avec d’autres objets, tel que InputStreamReader, afin de bénéficier des
avantages proposées par certaines classes de lecture de caractères.
Exemple de code pour lire une ligne au clavier :
BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in));
buffer.readLine();
La saisie au clavier étant des caractères, l’attribut in de la classe System est de type InputStream par
soucis de rétrocompatibilité, du fait que dans les premières versions de Java il n’y avait pas d’objet
Reader pour manipuler les flux de caractères. Afin d’apporter des fonctionnalités supplémentaires à
la lecture d’une chaîne de caractères, on privilégiera la classe Console, car elle permet d’avoir des
méthodes de saisie de mot de passe sans affichage à l’écran.
2. Classe Console
La classe Console permet d’accéder à la console de la machine virtuelle. Une telle classe a été
introduite à partir de la version 6 du jdk. Il n’y a qu’une instance de console par machine
virtuelle. Il est possible de récupérer l’instance de la console à travers la méthode console() de la
classe System.
De nombreuses méthodes sont mises à disposition du développeur afin de lire une chaine de
caractères au clavier :
- String readLine() :
Lit une ligne de texte provenant de la console.
- String readLine(String fmt, Object… args) :
Affiche un prompt formaté, puis lit une ligne de texte provenant de la console.
- char[] readPassword() :
Lit un mot de passe sans affichage des caractères tapés.
- char[] readPassword(String fmt, Object… args) :
Affiche un prompt formaté, puis lit un mot de passe sans affichage des caractères tapés.
Exemple de codes :
System.console().readLine();
System.console().readPassword();
3.2.2. Afficher à l’écran
Deux principales méthodes se distinguent en Java pour l’affichage à l’écran d’une chaine de
caractères.
1. Classe System
La classe System permet d’avoir un accès direct à la sortie de l’écran. Pour cela, on utilisera
l’attribut statique out qui nous est fourni par cette classe. L’attribut out étant de type PrintStream, il
sera possible d’utiliser de nombreuses méthodes de formatage des octets, comme println(), print(),
format()… Pour avoir plus de détails sur ces méthodes, vous vous référerez à la documentation ci-
jointe : http://docs.oracle.com/javase/1.5.0/docs/api/java/io/PrintStream.html.
Exemple :
System.out.println(“Ce message s’affiche à l’écran avec un retour à la ligne”);
System.out.print(“Ce message s’affiche à l’écran sans retour à la ligne”);
System.out.println(“Ce message affiche la valeur de la variable d” + d + “ avec
un retour à la ligne.”);
Le problème d’utiliser la classe System pour afficher des informations sur la console est qu’elle ne
prend pas en charge l’encodage de la console qui est dépendant du système. Pour faire des
affichages interopérables, il sera plus judicieux d’utiliser la classe Console.
2. Classe Console
La classe Console permet d’accéder à la console de la machine virtuelle. Une telle classe a été
introduite à partir de la version 6 du jdk. Il n’y a qu’une instance de console par machine
virtuelle. Il est possible de récupérer l’instance de la console à travers la méthode console() de la
classe System. La méthode writer() de la classe Console retourne un objet de type PrintWriter. Il
sera possible d’utiliser de nombreuses méthodes de formatage des chaines de caractères, comme
println(), print(), format()… Pour avoir plus de détails sur ces méthodes, vous vous référerez à la
documentation ci-jointe :
http://docs.oracle.com/javase/6/docs/api/java/io/PrintWriter.htmlhttp://docs.oracle.com/javase/6/doc
s/api/index.html?java/lang/System.html.
Exemple de codes :
System.console().writer().println("Affichage de ce texte sur la console avec
retour à la ligne");
System.console().writer().print("Affichage de ce texte sur la console sans
retour à la ligne");
4. Serveur web simplifié
Afin de mettre en pratique les concepts de la programmation réseau vue en cours, nous allons
mettre en place dans un premier temps un serveur web simplifié. Ce serveur web répondra
uniquement aux commandes de récupération d’une page web (méthode Get) et de dépose d’une
page web sur le serveur (méthode Put). Le client sera donc susceptible à travers ces commandes
d’interagir avec le serveur web.
4.1. Implémentation du serveur
Le serveur web écoutera les connexions entrantes sur le port 5100. Il traitera les connexions les
unes après les autres. En fonction du traitement demandé (méthode Get ou Put), le serveur web
répondra en renvoyant un code retour désignant si l’opération s’est correctement passée ou au
contraire elle a échoué.
4.1.1. Méthode GET
Une fois que la demande de connexion du client est acceptée, le serveur se positionne en lecture
pour attendre la requête HTTP. La requête transmise par le client contient le nom de la méthode
utilisée, le fichier souhaité et la version du protocole HTTP utilisé. Elle contiendra également l’hôte
qui est contacté.
Un exemple de requête GET est fourni dans l’encadré suivant :
GET fichier.html HTTP/1.1
Host: www.serveur.fr
Une telle requête peut échouer uniquement si le fichier demandé n’existe pas. Si le fichier existe, le
serveur renverra un code de retour égal à 200, fournira la taille du fichier dans le champ
content-length et enverra le contenu du fichier.
Un exemple d’une telle réponse est fourni dans l’encadré suivant :
HTTP/1.1 200 Ok
Content-Length: taille_du_fichier_en_octet
<HTML>
…
Contenu du fichier
…
</HTML>
Si le fichier n’existe pas, le serveur renvoie un code d’erreur 404. Il fournira la taille du fichier
d’erreur dans le champ content-length et enverra le contenu de ce fichier.
HTTP/1.1 404 Not Found
Content-Length: taille_du_fichier_d’erreur_en_octet
<HTML>
…
Contenu du fichier
…
</HTML>
Vous définirez la méthode sendGetResponse qui a le prototype suivant :
public void sendGetResponse(String nomFichier, Socket socketClient);
Cette méthode permet de lire la fin de la requête GET, dans le socket défini par socketClient, et de
renvoyer le bon message (code retour 200 ou 404) en fonction que le fichier existe ou non. Vous
pourrez utiliser la méthode public String[] split(String regex) de la classe String qui
permet à partir d’un String de le découper en petits morceaux en fonction du délimiteur spécifié
dans le paramètre regex.
4.1.2. Méthode PUT
La méthode PUT du protocole HTTP permet de déposer un fichier sur le serveur. Une fois que la
demande de connexion du client est acceptée, le serveur se positionne en lecture pour attendre la
requête HTTP. La requête transmise par le client contient le nom de la méthode utilisée, le nom du
fichier à sauvegarder et la version du protocole HTTP utilisé. Elle contiendra également l’hôte qui
est contacté, la taille du fichier, et enfin son contenu.
Un exemple de requête PUT est fourni dans l’encadré suivant :
PUT fichier.html HTTP/1.1
Host: www.serveur.fr
Content-length: taille_du_fichier_en_octet
<HTML>
…
Contenu du fichier
…
</HTML>
Une telle requête réalise un traitement différent en fonction de l’existence du fichier sur le serveur
ou non. Si le nom du fichier existe déjà dans le répertoire courant du serveur, il renverra un code de
retour égal à 200. Le fichier verra ainsi son contenu remplacé par le contenu fourni dans la requête.
Un exemple d’une telle réponse est fourni dans l’encadré suivant :
HTTP/1.1 200 Ok
Si le fichier n’existe pas, le serveur renvoie un code d’erreur 201. Ce code indiquera que le fichier a
été créé.
Un exemple d’une telle réponse est fourni dans l’encadré suivant :
HTTP/1.1 201 Created
Vous définirez la méthode sendPutResponse qui a le prototype suivant :
public void sendPutResponse(String nomFichier, Socket socketClient);
Cette méthode permet de lire la fin de la requête PUT, dans le socket défini par socketClient, et de
renvoyer le bon message (code retour 200 ou 201) en fonction de l’existence ou non du fichier.
Vous pourrez utiliser la méthode public String[] split(String regex) de la classe String qui
permet à partir d’un String de le découper en petits morceaux en fonction du délimiteur spécifié
dans le paramètre regex.
4.2. Implémentation du client
Le client va être l’application qui initie la connexion au serveur et qui va envoyer les requêtes
souhaitées. Pour cela, un petit menu sera mis à disposition de l’utilisateur, afin qu’il choisisse
l’opération qu’il veut réaliser (Get ou Put).
4.2.1. Création d’un menu
Le menu, affiché à l’utilisateur, comportera trois choix : quitter le programme, recevoir un fichier,
émettre un fichier. En fonction du choix de l’utilisateur, l’opération souhaiter sera réalisée. Le menu
sera réaffiché tant que l’utilisateur ne tapera pas un des choix demandé.
Exemple de menu :
Veuillez saisir l’opération à réaliser :
0 – Quitter le programme
1 – Recevoir un fichier (méthode Get)
2 – Emettre un fichier (méthode Put)
Le menu sera une boucle qui sera réaffichée tant que l’utilisateur ne saisira pas 0. Pour cela, vous
utiliserez une boucle do{ … }while(…); . Vous vérifierez que la chaine saisie soit bien un entier,
en utilisant la méthode suivante de la classe Integer :
public static int parseInt(String s)
throws NumberFormatException
Cette méthode retourne un entier si la chaîne en paramètre contient uniquement des chiffres, sinon
retourne une exception de type NumberFormatException. Vous traiterez une telle exception afin de
spécifier à l’utilisateur qu’il doit saisir un chiffre compris entre 0 et 2.
4.2.2. Méthode GET
Lorsque l’utilisateur a choisi l’opération Get, vous lui demanderez le nom du serveur sur lequel se
connecter puis le nom du fichier à afficher.
Exemple de saisie des paramètres nécessaires au bon fonctionnement de la méthode Get :
Vous désirez recevoir le contenu d’un fichier.
Veuillez saisir le nom du fichier à consulter :
nomFichier.html
Veuillez saisir le nom du serveur :
www.serveur.fr
Une fois que ces paramètres sont saisis par l’utilisateur, le programme ouvrira une connexion avec
un socket sur le port 5100 du serveur et enverra l’entête de la requête HTTP au serveur.
Exemple d’entête HTTP émise :
GET nomFichier.html HTTP/1.1
Host: www.serveur.fr
Une fois que l’entête est envoyé le client se positionne en attente de réponse de la part du serveur
(méthode read) et affiche le contenu de la réponse sur l’écran de l’utilisateur.
Vous définirez la méthode sendGetRequest qui a le prototype suivant :
public void sendGetRequest();
4.2.3. Méthode PUT
Lorsque l’utilisateur a choisi l’opération Put, vous lui demanderez le nom du serveur sur lequel se
connecter puis le nom du fichier à envoyer.
Exemple de saisie des paramètres nécessaires au bon fonctionnement de la méthode Get :
Vous désirez recevoir le contenu d’un fichier.
Veuillez saisir le nom du fichier à envoyer :
nomFichier.html
Veuillez saisir le nom du serveur :
www.serveur.fr
Une fois que ces paramètres sont saisis par l’utilisateur, le programme ouvrira une connexion avec
un socket sur le port 5100 du serveur, enverra l’entête de la requête HTTP au serveur et le contenu
du fichier. Bien entendu, le client devra vérifier, au préalable, que le fichier existe bien, sinon il
renverra un message d’erreur comme quoi le nom du fichier est inexistant (aucune connexion n’aura
besoin d’être établie).
Exemple d’entête et contenu de la requête HTTP émise :
PUT nomFichier.txt HTTP/1.1
Host: www.serveur.fr
Content-length: 10
Test 1234
Une fois que la requête est envoyée le client se positionne en attente de réponse de la part du
serveur (méthode read) et affiche le contenu de la réponse sur l’écran de l’utilisateur. Il affichera si
le fichier a été créé ou s’il a été modifié.
Vous définirez la méthode sendPutRequest qui a le prototype suivant :
public void sendPutRequest();
4.3. Compilation et exécution
Une fois le programme serveur et le programme client réalisés, il ne vous reste plus qu’à les
compiler. Pour cela, placez-vous dans le répertoire courant, et compiler l’ensemble des classes :
javac *.java
Une fois les classes compilées, il ne vous reste plus qu’à exécuter le serveur et le client avec la
commande (on suppose que le programme serveur s’appelle serveurWeb et que le programme
client s’appelle clientWeb) :
java serveurWeb
java clientWeb
4.4. Test du serveur Web
Vous testerez chaque cas de fonctionnement pour le serveur et pour le client. Une fois le serveur
lancé, vous exécuterez le client, et vous testerez les scénarios suivants :
- Méthode Get sur un fichier inexistant
- Méthode Get sur un fichier existant
- Méthode Put avec un fichier non créé sur le serveur
- Méthode Put avec un fichier déjà créé sur le serveur
- Méthode Put avec un fichier non existant sur le client
5. Serveur Web multitâche
Afin de mettre en œuvre un fonctionnement multitâche du serveur, ce dernier doit être adapté pour
traiter plusieurs connexions simultanément. Le programme client restera inchangé. Seul le serveur
voit son fonctionnement changer.
5.1. Gestion des threads
A partir du programme serveur précédent, vous allez implémenter un fonctionnement multitâche du
serveur. Pour cela, vous créerez une classe serveurWebThread qui implémentera l’interface
Runnable. Vous renseignerez la fonction run() de cette classe, en prenant le corps du code
précédent et en l’adaptant à ce nouveau type de fonctionnement.
L’objet de la classe serveurWebThread devra prendre en paramètre un socket qui représentera
la connexion établie avec le client.
5.2. Implémentation du serveur
Vous allez créer une nouvelle classe pour le serveur web qui appellera la classe
serveurWebThread. Pour chaque connexion établie avec le client, le serveur instanciera un nouvel
objet Thread, puis exécutera la méthode run() de la classe serveurWebThread.
5.3. Compilation et exécution
Comme pour la session précédente, vous recompilerez le nouveau serveur avec l’utilitaire javac.
Une fois le serveur recompilé vous pourrez lancer la nouvelle version du serveur et le client
anciennement créé avec l’utilitaire java.
5.4. Test du serveur Web
Afin de tester le fonctionnement multitâche du serveur, vous lancerez plusieurs clients en même
temps. Vous testerez chaque cas de fonctionnement pour le serveur et pour chaque client. Comme
pour la partie précédente, vous testerez les scénarios suivants :
- Méthode Get sur un fichier inexistant
- Méthode Get sur un fichier existant
- Méthode Put avec un fichier non créé sur le serveur
- Méthode Put avec un fichier déjà créé sur le serveur
- Méthode Put avec un fichier non existant sur le client