TP 5 : Programmation réseau en Java

publicité
TP 5 : Programmation réseau en Java
[email protected]
Les TPs sont individuels. A la demande de l’enseignant vous envoyez (par mail)
vos fichier *.java ainsi qu’un petit compte rendu expliquant ce que vous avez fait.
Lorsqu’un TP est composé de plusieurs exercices, l’enseignant vous demandera de
changer d’exercice au fur et à mesure de la séance même si vous n’avez pas fini
votre exercice. Ainsi vous pourrez aborder toutes les notions présentées dans le sujet.
Pendant les séances de TP, placez Eclipse sur l’un de vos bureau, OpenOffice sur
un autre et Firefox sur un troisième. Firefox vous permet de consulter (exclusivement !)
les pages de documentations de Sun : http ://java.sun.com/j2se/1.5.0/docs/api/
But du TP
Dans ce TP vous allez travailler avec les composants réseaux de Java. Dans
un premier temps, vous allez construire un serveur et un client élémentaires
puis vous allez les modifiez pour utiliser le protocole HTTP.
1 Bases théoriques
1.1 Les flux et les fichiers en Java
Dans les lignes qui suivent les mécanismes de base des fichiers et des flux vont
être présentés. Cette présentation n’est absolument pas exhaustive, elle permet
juste de pouvoir aborder la suite du TP sereinement...
1.1.1
Les fichiers
Les fichiers sont supportés dans Java par la classe File. Pour créer un objet
File, le plus simple et de passer le nom du fichier en paramètre du constructeur. On dispose ensuite de nombreuses méthodes de manipulation de fichier : lecture des bits d’états (isHidden, canRead, canWrite), manipulation (create, mkdir,
...)...
1.1.2
Les flux “élémentaires”
L’accès aux donnés extérieures est assuré par des flux. Les flux matérialisent
des données sortantes ou entrantes, cette représentation est basée sur deux classes
abstraites : InputStream pour les données entrantes et OutputStream pour les
données sortantes.
Les descendants de ces classes permettent d’accéder à des données depuis
différentes sources. Par exemple, FileInputStream et FileOutputStream permettent de construire des flux entrants et sortants à partir du objet File. Pour
1
lire des données à partir d’un FileInputStream on utilise la méthode read qui
renvoie un octet, pour écrire un octet on utilise la méthode write de la classe
FileOutputStream.
1.1.3
Les flux “enveloppants”
Souvent, les données à lire ou à écrire sont stockées sous un format plus
complexe que les octets, ce peut être des caractères, des chaînes de caractères,
des nombres... Java propose des classes qui convertissent des données complexes
(chaînes de caractères, chiffres...) pour les envoyer dans un flux élémentaire.
Pour stocker les types élémentaires (entiers, flottants, booléen,...) les méthodes
DataInputStream et DataOutputStream proposent des méthodes de conversion.
Les deux classes les plus utilisées sont InputStreamReader et OutputStreamWriter.
InputStreamReader convertit les octets du flux entrant en caractères 1 accessibles
via les méthodes read. Réciproquement, OutputStreamWriter permet de convertir
des caractères en octets à destination d’un flux sortant.
Pour manipuler des objets de type String, Java propose deux classes de conversion : BufferedReader qui convertit les données entrantes d’un InputStreamReader
en String et BufferedWriter qui convertit les objets String pour les envoyer
dans un OutputStreamWriter. Les codes sources 1 et 2 présentent l’utilisation de
ces classes pour manipuler des fichiers textes.
Code source 1 Lecture de chaînes de caractères
File fichier = new File("Document.txt");
FileInputStream fi = new FileInputStream(fichier);
InputStreamReader isr = new InputStreamReader(fi);
BufferedReader br = new BufferedReader(isr);
String chaineLue = br.readLine();
System.out.println(chaineLue);
br.close();
isr.close();
fi.close();
L’écriture de chaînes de caractères peut encore être simplifiée en utilisant la
classe PrintWriter. Le constructeur admet soit un File, un nom de fichier ou un
OutputStream. On dispose ensuite de méthodes comme print et println pour
écrire des chaînes de caractères.
1.2 Le réseau TCP en Java
La gestion des communications réseaux en Java est assurée par des flux. Une
fois la connexion établie, l’envoie et la réception de données utilisent les mêmes
1
La conversion n’est pas aussi triviale qu’elle n’y parait. Selon, l’encodage choisi, un caractère peut
prendre plusieurs octets.
2
Code source 2 Écriture de chaînes de caractères
File fichier = new File("Sortie.txt");
FileOutputStream fo = new FileOutputStream(fichier);
OutputStreamWriter osr = new OutputStreamWriter(fo);
BufferedWriter bw = new BufferedWriter(osr);
String maChaine;
maChaine = "Bonjour";
bw.write(maChaine);
bw.flush();
bw.close();
osr.close();
fo.close();
méthodes que les fichiers. Dans une connexion réseau on distingue deux entités
différentes : le (ou les) client(s) et le serveur.
1.2.1
Les clients
La classe Socket du package java.net permet d’établir une connexion entre
deux ordinateurs. L’ordinateur attendant une connexion est le serveur, celui demandant une connexion est le client.
Un des constructeurs permet de spécifier le nom de l’hôte et le port de connexion
lors de la création de l’objet.
Une fois la connexion établie, les données envoyées et reçues sont accessibles
via les flux entrant et sortant de ce Socket. Les méthodes getInputStream et
getOuputStream permettent d’accéder à ces flux pour envoyer ou recevoir des données. Elles renvoient des objets descendants de InputStream et OutputStream.
Pour fermer un Socket et donc terminer la connexion, on utilise la méthode
close.
1.2.2
Les serveurs
Une application serveur attend un client pour établir une connexion sur un port
donné. Pour construire un serveur sur un port on utilise la classe ServerSocket.
Le constructeur n’admet qu’un paramètre, le numéro du port. Ensuite, on utilise
la méthode accept qui bloque l’application tant qu’un client n’a pas demandé de
connexion. Cette méthode renvoie un objet socket représentant la connexion sur le
client une fois la connexion établie. Ensuite, le programme se comporte de la même
manière que pour le client, les méthodes getInputStream et getOutputStream
étant utilisées pour accéder aux flux. La méthode getInetAdress permet de connaître
l’adresse du client. A la fin des transactions, les deux sockets (le ServerSocket et
le socket client doivent être fermés en utilisant la méthode close).
3
1.2.3
Le protocole UDP
Java permet aussi de communiquer à l’aide du protocole UDP. Pour cela, on
utilise la classe DatagramSocket pour créer le socket de connexion et la classe
DatagramPacket pour créer le paquet de données à envoyer. Nous n’irons pas plus
loin dans le cadre de ce TP concernant le protocole UDP.
1.3 Introduction au protocole HTTP
1.3.1
Formatage des adresses
Les adresses internet sont de la forme http ://serveur/document. Par exemple
les pages de documentation de Java se situent sur http ://java.sun.com/j2se/1.5.0/docs/a
L’entête http représente le protocole utilisé (HTTP, HTTPS, FTP, ... selon les sites).
Le nom du serveur commence souvent par www mais ce n’est pas obligatoire (voir
l’exemple proposé où c’est java.sun.com) enfin, le reste de l’adresse correspond au
chemin pour atteindre le document.
1.3.2
Le protocole HTTP
Le protocole HTTP (Hyper Text Transfert Protocole) est le protocole majoritairement utilisé par les serveurs Web. Il est souvent basé sur une connexion TCP sur
le port 80. Le serveur et le client s’échange les données sous forme de chaînes de
caractères non cryptées. Les commandes sont peu nombreuses et simples d’utilisation. Les commandes peuvent être suivies de paramètres (nommés champs) sous
la forme Champ : valeur à raison d’un champ par ligne. Après chaque commande
envoyée par le client le serveur envoie une réponse (composée d’un code, de champs
et du corps de la réponse chaque partie étant séparée par une ligne vide) et clôt la
connexion. Un échange est donc de la forme :
Client :
Serveur :
Serveur :
COMMANDE
Champ : Valeur
Champ : Valeur
HTTP :/x.x Code Explication
Champ : Valeur
Champ : Valeur
Corps de la réponse
Fermeture de la connexion
La commande GET C’est la commande la plus utilisée. Elle permet au client de
demander un document (ou une ressource) situé à l’URL passée en paramètre suivie
du protocole choisi (HTTP/1.0 ou HTTP/1.1). La commande GET ne nécessite pas de
paramètre. Pour passer des variables au serveur on les encode à la suite de l’URL
à l’aide du caractère ?. Par exemple pour ajouter la variable a=10 et b=12 à l’url
http ://monsite/mapage.php on écrira http ://monsite/mapage.php?a=10&b=12.
4
Exemple d’utilisation : Après s’être connecté au site java.sun.com, on demande la page d’accueil2 en envoyant la chaîne de caractères :
GET / HTTP/1.0
suivie d’une ligne vide (retour chariot).
Le serveur renvoie alors diverses information 3 :
HTTP/1.1 200 OK
Server : Sun-Java-System-Web-Server/6.1
Date : Mon, 12 Dec 2005 09 :47 :12 GMT
...
Connection : close
< !DOCTYPE HTML PUBLIC ...>
<html>
...
</html>
La première ligne renvoyée est une ligne représentant le statut de la demande.
Elle commence par HTTP suivie de la version du protocole supportée puis des codes
représentant l’état du traitement de la requête. Le code retour le plus courant est
200 OK qui signifie que la transaction s’est bien déroulée. Quelques codes sont
bien connus des internautes comme 404 Not found qui est envoyé lorsque l’on
demande un document qui n’existe pas ou 403 Forbidden qui est renvoyé lorsque
le client demande une ressource à laquelle il n’a pas accès.
Les lignes suivantes sont de la forme Champ : Valeur. On retrouve le nom du
serveur, la date de la demande, ... Plusieurs champs sont souvent utilisés par le
client :
– Content-Length : C’est la taille en octets du corps de la réponse.
– Content-Type : Le type MIME de la réponse est renvoyé (par exemple : text/html,
image/png, ...). Le client utilise cette information pour pouvoir interpréter correctement les données de la réponse.
– Last-Modified : Certains serveurs renvoient la date de la dernière modification du document, cette information est utilisée pour gérer les caches sur les
navigateur en complément de la commande HEAD (voir ci-dessous).
Une ligne blanche est insérée entre les champs et le corps du message. Ensuite, le
document demandé est envoyé, dans l’exemple ci dessus, c’est une page HTML.
La commande HEAD La commande HEAD est identique à la commande GET sauf
que le serveur ne renvoie que l’entête du message (donc seulement le statut de la
demande et les champs). Elle est utilisée pour vérifier qu’une page a été modifiée
par rapport à celle présente dans le cache du navigateur avant de la télécharger.
La commande POST POST a un fonctionnement proche de GET mais permet d’envoyer des données dans le corps de la demande. Cette méthode est souvent utilisée
pour envoyer des données sans modifier l’adresse ou pour envoyer des données trop
2
3
La page d’accueil est généralement référencée par /.
Seules quelques informations importantes ont été recopiées, le reste a été remplacé par des ...
5
longues pour être passées dans l’adresse. Les champs envoyés comprennent (au minimum) la taille du corps ainsi que l’encodage utilisé au format MIME. Par exemple,
pour envoyer les données a=10 et b=12 à l’url http ://monsite/mapage.php on
écrira :
POST /mapage.php HTTP/1.0
Content-Type : application/x-www-form-urlencoded
Content-Length : 9
suivie d’une ligne vide
a=10&b=12
suivie d’une ligne vide
Pour envoyer une image, on utilisera la même approche
POST /saveimage.php HTTP/1.0
Content-Type : image/jpeg
Content-Length : 32768
suivie d’une ligne vide
Les octets composant l’image
suivie d’une ligne vide
Les deux méthodes (POST et GET) sont assez proche l’une de l’autre. Elles renvoient toutes les deux le même type de résultat. Les différences sont les suivantes :
– La méthode GET ne permet d’envoyer qu’une faible quantité de données car
elles sont ajoutées à l’URL (qui a une taille limité).
– Généralement, les résultats de la méthode GET sont mis en cache par les navigateurs ce qui n’est pas le cas des résultats de la méthode POST (d’où les
messages du type “La page que vous tentez... données POSTDATA...” de Firefox).
1.3.3
Test du protocole avec telnet
Pour comprendre le protocole HTTP nous allons faire quelques essais avec le
logiciel telnet. telnet est un protocole et un logiciel utilisant ce protocole pour se
connecter à une machine distante. Pour cet exercice, nous allons utiliser telnet
pour se connecter à une machine distante herbergeant un site internet.
Connectez vous à un serveur web (java.sun.com par exemple) en utilisant
telnet dans un terminal :
telnet serveur 80
Si la connexion s’est bien passée, vous obtenez un message du type :
Trying XX.XX.XX.XX
Connected to serveur (XX.XX.XX.XX)
Escape character is ’^]’
A partir de maintenant, toutes les chaînes que vous entrez sont envoyées au serveur après l’appui sur entrée (ou un caractère retour chariot). Utilisez la commande
GET pour obtenir une page web.
6
2 Travail à réaliser
Pour ce TP, créez un nouveau projet sous Eclipse. Ce TP est composé de trois
parties, la création d’un serveur simple, d’un client et d’un serveur Web simple.
2.1 Un serveur simple
Nous allons construire un serveur simple local (sur 127.0.0.1) et nous allons
utiliser le programme telnet comme client.
2.1.1
Création des sockets
Créez une nouvelle classe (par exemple Serveur) qui contient une méthode
main. Dans cette classe ajoutez un constructeur. La première chose à faire lors
de la construction de l’objet et de créer un objet ServerSocket sur un port libre
(par exemple 2500) :
ServerSocket sock = new ServerSocket(2500);
Dès que vous ajoutez ces lignes, Eclipse signale la présence d’une éventuelle
exception. Choisissez l’option qui permet de gérer cette exception (Surround with...)
et ajoutez un message d’erreur pertinent dans la partie catch.
A partir de maintenant tout votre code sera situé entre les accolades qui suivent
try.
Votre programme doit attendre qu’un client se connecte et ensuite il doit récupérer le socket sur ce client. Tout ceci se fait à l’aide de la méthode accept de
l’objet ServerSocket. Cette méthode (bloquante) attend un client et renvoie un objet Socket sur le premier client connecté. Attendez un client et affichez son adresse
IP puis fermez les Socket grâce à la méthode close.
Pour tester cette partie, ajoutez un objet Serveur dans le main. Lancez votre programme puis dans une console, lancez telnet 127.0.0.1 2500. Cette instruction
exécute le programme telnet en client sur l’adresse local sur le port 2500. Normalement, votre application doit vous afficher l’adresse IP du client (ici 127.0.0.1).
Tuez votre application pour continuer le travail.
2.1.2
Ouverture du flux sortant
Nous allons créer le flux sortant qui envoie des données du serveur vers le client.
Un objet PrintWriter construit4 à partir du flux sortant permet d’écrire dans le
flux en utilisant les méthodes print et println. :
PrintWriter pwSock = new PrintWriter(socketClient.getOutputStream(),
true) ;
Dès que le client se connecte, le serveur doit lui envoyer la chaîne “Bonjour” :
pwSock.println(“Bonjour”);
4
Le second paramètre du constructeur est un booléen que l’on place à true pour forcer la purge
du buffer à chaque ajout de ligne.
7
2.1.3
Ouverture du flux entrant
La classe Socket renvoie un InputStream représentant le flux entrant à l’aide
de la méthode getInputStream. Un InputStream ne peut pas être directement utilisé pour accéder aux données. On doit utiliser un objet de la classe InputStreamReader
construit à partir de ce flux pour pouvoir obtenir des caractères (la classe InputStreamReader
convertit les octets en caractères) :
InputStreamReader isrSock = new InputStreamReader(client.getInputStream());
Créez un tableau de caractères (char) de 10 éléments. Passez ce tableau en
paramètre de la méthode read de l’objet isrSock. Ajoutez une ligne pour afficher
le contenu du tableau.
Testez de nouveau votre programme avec telnet. Une fois telnet lancé, tapez
quelques caractères, ils doivent s’afficher dans la console d’Eclipse. Que se passet-il si vous tapez 5 caractères, 10 caractères ?
Cette approche ne permet de lire que quelques caractères. L’utilisation d’un
BufferedReader construit à partir du InputStreamReader permet de lire des
lignes de caractères sous la forme de String.
BufferedReader brSock = new BufferedReader(isrSock);
La méthode readLine de l’objet brSock renvoie la chaîne lue. Affichez la et
testez de nouveau votre programme.
Construisez une boucle qui lit une chaîne et l’affiche tant que la chaîne n’est
pas Bye.
2.2 Un client simple
Nous allons créer un client simple sur le port 80 (port HTTP) d’un serveur web
quelconque.
2.2.1
Création du socket et des flux
Reprenez le travail précédent (création d’un socket, création du flux entrant,
création du flux sortant) pour vous connecter à un site web quelconque.
2.2.2
Construction d’une requête HTTP
Modifiez votre programme pour envoyer une requête HTTP sur une page de ce
site web, affichez le résultat de la requête.
2.3 Un serveur web simple
Pour cet exercice, reprenez le premier exercice du TP (serveur simple).
2.3.1
Recherche de la chaîne GET
La méthode startsWith de la classe String permet de vérifier le début d’une
chaîne. Utilisez la pour retrouver GET dans la chaîne reçue par le serveur. Quand
cette chaîne est reçue, envoyez le statut de la demande (200 OK), puis les champs :
Content-Type : text/html
8
Connection : close
Respectez bien les sauts de ligne nécessaires. Puis envoyez une page web simple
composée de la chaîne : “<html>Bonjour</html>”
Pour tester votre programme utilisez Firefox sur le site http ://localhost :2500.
Modifiez votre programme pour afficher toutes les chaînes envoyées par le client.
2.3.2
Analyse de la requête
En utilisant les méthodes startsWith et contains de la classe String, modifiez votre serveur web pour pouvoir afficher trois pages différentes (les pages
peuvent être, par exemple, page1, page2 et page3). Affichez un message différent
dans chaque page pour vérifiez votre programme.
2.3.3
Utilisation de fichier
Stockez vos pages HTML sous la forme de fichiers. Modifiez votre programme
pour charger le bon fichier lors d’une requête, gérez l’erreur 404 (fichier non trouvé).
Avant de modifier votre serveur, manipulez des fichiers dans une petite classe temporaire.
9
Téléchargement