Tout au long de ce sujet, nous travaillons sur le programme
prog_http_client.cpp.
Les données véhiculées par le protocole HTTP peuvent être de nature
quelconque (texte, image, vidéo...).
Toutefois, HTTP est un protocole en mode texte ; ceci signifie que les
en-têtes des requêtes et des réponses sont des lignes de texte.
Bien que ce soit inutile dans la pratique, ceci donne l'opportunité à un être
humain de rédiger de telles lignes et de les lire.
Qui plus est, ce protocole applicatif utilise le protocole de transport
TCP.
Ces propriétés nous permettent d'utiliser l'outil
ncat comme un client
qui se connecte en TCP à un serveur HTTP pour expérimenter
“à la main”
le dialogue selon le protocole HTTP.
À titre d'exercice, nous allons chercher à obtenir le contenu de l'URI
http://crs-www.enib.fr/Sub/page.html.
Ceci doit se comprendre comme : se connecter au serveur HTTP
crs-www.enib.fr pour lui demander la ressource
/Sub/page.html.
Sauf indication contraire, le port par défaut d'un serveur HTTP est le port
TCP 80.
Nous invoquons donc la commande suivante :
$ ncat -C crs-www.enib.fr 80 ↵
Une fois connecté, le client doit formuler sa requête (consultez attentivement
l'exemple de requête
GET du document
Memo_HTTP.pdf).
Nous saisissons alors la première ligne de la requête HTTP :
GET /Sub/page.html HTTP/1.1
En HTTP/1.1, le champ
Host: est obligatoire dans la requête :
Host: crs-www.enib.fr:80
(nb: le port aurait pu être omis ici puisqu'il a la valeur par défaut).
En HTTP/1.1, une même connexion peut resservir pour plusieurs requêtes
successives alors qu'en HTTP/1.0, chaque connexion était fermée par le
serveur après l'envoi de la réponse HTTP.
Cet ancien mode de fonctionnement est considéré obsolète mais est toutefois
légèrement plus facile à utiliser dans le cadre de nos expérimentations ;
il suffit en effet de lire la réponse jusqu'à obtenir la fin-de-fichier sur
la connexion.
Nous en réclamons l'usage ainsi :
Connection: close
L'en-tête de notre requête HTTP contient maintenant le minimum nécessaire
(de nombreuses autres options sont disponibles) ; nous signalons alors
sa fin en ajoutant une ligne vide :
↵
Dès lors que le serveur reçoit cette ligne vide, il sait que l'en-tête de la
requête a été complètement exprimé, il peut alors produire sa réponse.
Toujours en vous référant au document
Memo_HTTP.pdf, vous devrez
reconnaître dans le terminal un en-tête HTTP de réponse suivi du contenu
utile (une page HTML ici).
Remarquez notamment que cet en-tête de réponse a une structure semblable à
celle de la requête :
- une première ligne ayant une forme particulière,
- un nombre variable de lignes d'options
(Nom_du_champ: valeur...),
- une ligne vide marquant la fin de l'en-tête.
Le contenu utile de la réponse commence immédiatement après cette ligne vide
de fin d'en-tête, et s'étend jusqu'à la fin-de-ficher (grâce à l'usage de
l'option
Connection: close).
Vous pouvez également tenter d'obtenir de la même façon le document
http://nginx.org/
Le serveur visé est situé à l'extérieur de l'ENIB.
Son contenu et son fonctionnement peuvent changer à tout instant
sans que ce sujet ne soit adapté en conséquence...
.
Cette première expérimentation est suffisante pour écrire un programme client
très simple permettant de télécharger une ressource avec le protocole
HTTP.
Le début du programme fourni commence par interpréter les arguments de la
ligne de commande et affiche les éléments extraits de l'URI (l'adresse de la
ressource HTTP).
$ ./prog_http_client http://crs-www.enib.fr/Sub/page.html output_file ↵
uri=http://crs-www.enib.fr/Sub/page.html
outputFileName=output_file
protocol=http
hostname=crs-www.enib.fr
port=80
resource=/Sub/page.html
--> writing to 'output_file'
Bien entendu, en l'état ce programme n'a aucun autre effet que cet affichage.
En complétant le points
{A-1}
,
{A-2}
et
{A-3}
vous
réaliserez les opérations usuelles consistant à établir et fermer une
connexion TCP vers l'hôte et le port désignés.
Le point
{A-4}
revient à produire des lignes de texte semblables à ce
que vous avez précédemment saisi dans
ncat pour formuler la requête
HTTP (ce qui concerne l'usage d'un
proxy sera ignoré pour
l'instant).
Puisque notre client HTTP pourra à terme s'adresser à des serveurs HTTP de
toutes sortes, il est recommandé de terminer les lignes de texte par
"\r\n" plutôt que simplement
"\n" au cas où un tel serveur
fonctionnerait sous Windows (ce système code les fins de ligne avec ces
deux caractères).
Au niveau du point
{A-5}
il faudra recevoir, ligne par ligne, l'en-tête
de la réponse HTTP.
Dans cet exemple simpliste nous ne chercherons pas à interpréter le contenu de
ces lignes ; la seule qui nous intéresse est la dernière (la ligne vide)
car nous savons que tout ce que nous obtiendrons au-delà sera le contenu
utile de la réponse (la ressource réclamée).
Il ne reste plus alors qu'à compléter le point
{A-6}
afin de recopier
tout ce qui nous parvient sur la connexion vers le fichier de notre
choix.
Une fois la fin-de-fichier atteinte sur la connexion (nous avons réclamé
l'option
Connection: close dans la requête), le fichier obtenu contient
enfin le contenu de la ressource réclamée.
Relancez le programme et vérifiez que le fichier obtenu contient bien le
document attendu (une page HTML).
$ ./prog_http_client http://crs-www.enib.fr/Sub/page.html output_file ↵
Utilisez à nouveau ce programme pour réclamer la ressource
http://crs-www.enib.fr/Sub/ENIB.jpg et vérifiez que le fichier
obtenu contient bien l'image attendue (avec la commande
display output_file par exemple, ou en cliquant dessus).
Vous pouvez également tenter d'obtenir de la même façon les documents
http://nginx.org/ et
http://nginx.org/nginx.png
Le serveur visé est situé à l'extérieur de l'ENIB.
Son contenu et son fonctionnement peuvent changer à tout instant
sans que ce sujet ne soit adapté en conséquence...
.
En l'état, notre réalisation montre que quelques lignes de code très simples
permettent d'obtenir des ressources quelconques fournies par un serveur
HTTP.
Même si le protocole est en mode texte (les en-têtes sont des lignes de
texte), les données transmises peuvent être de nature quelconque.
Documentation utile :
- Memo_HTTP.pdf
- crs::gethostbyname() → man 3 gethostbyname
- crs::socket() → man 2 socket
- crs::connect() → man 2 connect
- crs::close() → man 2 close
- crs::sendAll() → man 2 send
- crs::recvLine() → man 2 recv
- crs::recv() → man 2 recv
- crs::openW() → man 2 open
- crs::writeAll() → man 2 write