6.2 MODÈLE CLIENT-SERVEUR, PROTOCOLE HTTP

 

 

INTRODUCTION

 

Vous savez déjà qu’une page Web affichée dans le navigateur Web est décrite par un fichier texte ordinaire au format HTML qui est typiquement situé sur une machine distante, également appelée hôte. Pour être en mesure de localiser le fichier, le navigateur utilise une URL de la forme http://hostname/filepath structurée de la manière suivante:

HTTP Hypertext Transfer Protocol qui spécifie les conventions utilisées pour effectuer la communication entre le serveur Web et le client (navigateur)
hostname Nom d’hôte de la machine sur laquelle tourne le serveur HTTP. Ce nom d’hôte permet d’identifier le serveur de manière unique sur Internet. Il s’agit soit d’une adresse IP sous la forme de 4 nombres compris entre 0 et 255, par exemple 192.41.150.141, ou d’un alias tel que www.tigerjython.com.
filepath Le chemin d’accès au document HTML commence par un slash mais il est relatif au dossier racine du Site sur le serveur Web.


La communication entre le client et le serveur se déroule sur le mode requête-réponse (request-response) qui constitue l’un des principes les plus importants dans le domaine de la communication par ordinateur. Cela suppose que le serveur exécute un programme qui attend les requêtes des clients sur un port TCP spécifique (pour le Web, il s’agit par défaut du port 80).

L’échange de données comporte plusieurs phases distinctes détaillées dans les schémas ci-dessous:

Phase 1:

Le client crée un socket par lequel il se connecte au serveur. Le serveur accepte la connexion et mémorise l’adresse IP du client.


Phase 2:

Le client envoie une requête au serveur contenant le chemin d’accès à la ressource demandée.


Phase 3:

Le serveur analyse et traite la requête puis envoie au client la ressource demandée, par exemple un fichier HTML..


Phase 4:

Le client (navigateur Web) reçoit la réponse et effectue le rendu de la page Web en l’affichant dans la fenêtre de navigation.



CONCEPTS DE PROGRAMMATION: Hôte, client, adresse IP, Modèle requête – réponse, Protocole HTTP, Analyse syntaxique (parsing)

 

 

REQUÊTE HTTP POUR OBTENIR UNE PAGE WEB

 

Le programme suivant effectue les phases 1, 2 et 4 pour obtenir le fichier welcome.html situé dans le sous-dossier py à la racine du serveur Web.

La méthode socket() de la classe socket stocke dans la variable s un objet socket. Cette méthode prend deux paramètres qui sont des constantes et qui indiquent le type de socket à créer.

import socket
 
host = "www.tigerjython.ch"
port = 80

# Phase 1
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host , port))

# Phase 2
request = "GET /py/welcome.html HTTP/1.1\r\nHost: " + host + "\r\n\r\n"
s.sendall(request)

# Phase 4
reply = s.recv(4096)
print("\nReply:\n")
print(reply)
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller)

 

 

MEMENTO

 

Lorsqu’un navigateur Web (client HTTP) demande une page Web, il le fait en utilisant le protocole HTTP. Il s’agit d’une série de conventions, suivies par le client et le serveur, déterminant de manière très précise les procédures à utiliser pour l’échange de données. La commande (on parle de méthode dans le jargon du HTTP) GET est définie de la manière suivante dans la spécification du protocole [plus.. Les protocoles liés à l’Internet sont discutés et décrits sous la forme de documents appelés RFC (Request For Comments). Le protocole HTTP est défini par la RFC 2616]:

Ligne 1

GET /py/welcomeex.html HTTP/1.1\r\n          

Commande (méthode) permettant de rapatrier le fichier welcome.html situé dans le dossier dossier py à la racine du site, suivie de la version du protocole et des caractères spéciaux <retour chariot><retour à la ligne> (représentés par les séquences d’échappement \r\n)

Ligne 2

Host: hostname\r\n     

Spécifie le nom d’hôte du serveur à contacter suivi des caractères spéciaux <retour chariot><retour à la ligne> représentés par les séquences d’échappement \r\n

Ligne 3 \r\n    

Ligne blanche ne comportant que le saut de ligne \r\n

 

 

UTILISATION DE HTTPS

 

Au lieu de HTTP, de plus en plus de sites Web nécessitent le protocole HTTPS, où les données sont cryptées à l'aide de SSL (Secure Socket Layer). Pour un tel serveur, une requête HTTP sur le port 80 pourrait produire un message d'erreur, comme dans le programme suivant pour le serveur github.com:

import socket 
 
host = "www.github.com"
port = 80

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host , port))
request = "GET /py/welcome.html HTTP/1.1\r\nHost: " + host + "\r\n\r\n" 
s.sendall(request)
reply = s.recv(4096)
print("\nReply:\n")
print(reply)
Highlight program code (Ctrl+C to copy, Ctrl+V to paste)

La réponse est:

HTTP/1.1 301 Moved Permanently
Content-length: 0
Location: https://www.github.com/py/welcome.html
  

Mais il est très facile de modifier la requête en HTTPS. Pour cela, changez le port sur 443 et insérez une ligne avec ssl.wrap_socket ().

import socket 
import ssl
 
host = "github.com"
port = 443

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host , port))
s = ssl.wrap_socket(s)
request = "GET / HTTP/1.1\r\nHost: " + host + "\r\n\r\n" 
s.sendall(request)
reply = s.recv(4096)
print("\nReply:\n")
print(reply)
Highlight program code (Ctrl+C to copy, Ctrl+V to paste)

 

 

EN-TÊTES HTTP ET CONNECT

 

La réponse du serveur est constituée d’en-têtes servant à préciser des informations d’état ainsi que le contenu à proprement parler qui est le document HTML. Avant de pouvoir effectuer le rendu du code HTML reçu, il faut donc supprimer les informations d’en-têtes pour ne conserver que le code HTML et le transmettre à un HtmlPane.

import socket
from ch.aplu.util import HtmlPane

host = "www.tigerjython.ch"
port = 80

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host , port))
request = "GET /py/welcome.html HTTP/1.1\r\nHost: " + host + "\r\n\r\n"
s.sendall(request)
reply = s.recv(4096)

index = reply.find("<html")
html = reply[index:]

pane = HtmlPane()
pane.insertText(html)
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller)

 

 

MEMENTO

 

La fonction recv(4096) rend un maximum de 4096 caractères d'une mémoire de données dans laquelle les caractères reçus sont copiés.

Pour supprimer les en-têtes de la réponse, on peut utiliser la méthode find(str) de la classe str permettant de rechercher la sous-chaine sub_str dans la chaîne str en retournant l’indice de la première occurrence. Si la sous-chaîne sub_str n’est pas trouvée dans str, la fonction retourne -1. Une fois que l’on connaît l’indice de la première occurrence de la sous-chaîne, on peut facilement supprimer tout ce qui la précède grâce à l’opérateur de slicing, str[start :] qui ne conserve que la partie de la chaine à partir de l’indice start jusqu’à la fin.

 

 

LECTURE DES PRÉVISIONS MÉTÉOROLOGIQUES

 

Vous vous demandez peut-être s’il est vraiment utile d’implémenter une procédure aussi compliquée pour afficher une page Web alors qu’il serait possible de n’écrire qu’une seule ligne ayant recours à la méthode insertUrl() du HtmlPane. Ce que vous avez appris par-là sera très utile si, au lieu de vouloir simplement afficher ce code HTML dans un HtmlPane, vous voulez en extraire une information bien précise. Le programme ci-dessous sert par exemple à lire le code HTML du site du bureau australien de météorologie pour en extraire les prévisions météorologiques actuelles.

Vous pouvez vous simplifier la vie encore davantage en utilisant la bibliothèque urllib2 au lieu de créer explicitement le socket permettant de télécharger le code HTML du site [plus... Une bibliothèque (on parle de module ou de package dans le jargon Python) ajoute une couche d’abstraction permettant de présenter à l’application des fonctions simples à invoquer alors qu’elles exécutent en fait un code compliqué à mettre en œuvre. Les informaticiens parlent d’une couche d’abstraction « haut niveau » au-dessus du code « bas niveau » sous-jacent].

Pour situer l’information désirée dans le code HTML, le programme d’analyse suivant représente le code HTML reçu à la fois dans la console de Python et dans le navigateur par défaut.

import urllib2
from ch.aplu.util import HtmlPane

url = "http://www.weatherzone.com.au"
HtmlPane.browse(url)
html = urllib2.urlopen(url).read()
print html
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller)

 

 

MEMENTO

 

L’utilisation d’une bibliothèque logicielle telle qu’urllib2 simplifie grandement le code mais présente le désavantage d’occulter les mécanismes de base nécessaires à l’implémentation de la fonctionnalité.

 

 

ANALYSE SYNTAXIQUE DE TEXTES

 

Vous êtes maintenant confrontés à la tâche fort intéressante et non triviale consistant à extraire l’information pertinente d’une longue de chaîne de caractères : c’est ce qu’on appelle l’analyse syntaxique du texte.

Dans un premier temps, il faut supprimer toutes les balises HTML à l’aide d’une fonction remove_html_tags(). Il s’agit d’une procédure bien typique dont l’algorithme peut être décrit de la manière suivante:

On parcourt le texte caractère à caractère en mémorisant deux états distincts : ou bien le caractère lu se trouve à l’intérieur d’une balise HTML ou il se trouve à l’extérieur. Il faut uniquement copier les caractères de la chaine analysée si l’on ne se trouve pas à l’intérieur d’une balise ouvrante ou fermante. Les changements d’état se produisent lorsque l’on est en train de lire les caractères < ou > marquant le début ou la fin d’une balise.

Avant d’être en mesure de développer une fonctionnalité permettant d’extraire l’information voulue, il faut analyser le contenu du texte après épuration des balises HTML. Cela consiste essentiellement à copier le texte dépourvu des balises HTML dans un éditeur de texte et à chercher une séquence de caractères marquant de manière unique le début de l’information recherchée ainsi qu’une séquence de caractères marquant la fin de cette information. La méthode str.find() sera alors d’un grand secours pour trouver l’indice start marquant le début de l’information. Pour trouver l’indice end marquant la fin de l’information, on réutilise str.find() en recherchant à partir de start. Pour ce site Web, la séquence de caractères du début est « State Weather  » et la séquence de fin est « National Summary  ». Le texte se trouvant entre les deux est extrait par une opération de slicing [start:end].

import urllib2

def remove_html_tags(s):
    inTag = False
    out = ""

    for c in s:
        if c == '<':
            inTag = True
        elif c == '>':
          inTag = False
        elif not inTag:
            out = out + c
    return out

url = "http://www.weatherzone.com.au"
html = urllib2.urlopen(url).read()
html = remove_html_tags(html)

start = html.find("National Summary")
end = html.find("State Weather", start)
html = html[start:end].strip()
print html
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller)

 

 

MEMENTO

 

The parsing of texts is usually done character by character. In many cases, however, methods of the string class may help as well [plus...les programmeurs expérimentés utilisent souvent la théorie des expressions régulières pour effectuer l’analyse syntaxique d’un texte].

En HTML, les caractères spéciaux et accentués sont encodés de manière spéciale à l’aide de séquences appelées entités HTML débutant par une esperluette & et se terminant par un point-virgule ; selon la table suivante:

<
&lt;
ê
&ecirc;
>
&gt;
ë
&euml;
à
&agrave;
î
&icirc;
á
&aacute;
ï
&iuml;
â
&acirc;
ô
&ocirc;
æ
&aelig;
œ
&oelig;
ç
&ccedil;
ù
&ugrave;
è
&eacute;
û
&ucirc;
é
&eacute;
ÿ
&yuml;

 

 

SYNTHÈSE VOCALE / LECTURE DE LA MÉTÉO

 

En utilisant les connaissances acquises dans un précèdent chapitre portant sur le son, vous êtes en mesure de créer un programme qui va utiliser la synthèse vocale pour lire automatiquement les prévisions météorologiques en quelques lignes de code:

from soundsystem import *

initTTS()
selectVoice("german-man")
sound = generateVoice(html)
openSoundPlayer(sound)
play() 
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller)

 

 

EXERCICES

 

1.


On trouve à l’adresse http://www.timeanddate.com de nombreuses informations intéressantes pouvant être extraites et réutilisées par un programme personnel. On peut par exemple en extraire le bulletin météo de n’importe quelle ville dans le monde. Visitez l’adresse http://www.timeanddate.com/weather/canada/halifax avec navigateur Web. Elle permet d’obtenir la température d’une ville choisie de manière arbitraire.

Déroulement : Sauver en intégralité le texte reçu lors de la visite de l’URL indiquée précédemment et y rechercher la température. Écrire ensuite un programme qui extrait cette valeur en utilisant de manière appropriée les méthodes de la classe str. Le programme doit permettre à l’utilisateur de sélectionner un pays et une ville à l’aide de boîtes de dialogue inputString() ou EntryDialog() et d’écrire la température du lieu en question dans la console ou dans un champ StringEntry de la boîte de dialogue.


2*.

La méthode urllib2.urlopen(url) lève une exception si l’URL n’existe pas. Si l’on enrobe cet appel d’une structure try-except permet de gérer l’erreur de manière douce en exécutant le bloc de la branch except en cas d’erreur au lieu de faire planter le programme.

try:
   urllib2.urlopen(url)
except:
   print "Error"

Modifier le programme de l’exercice 1 pour qu’il affiche un message d’erreur pertinent si la ville n’existe pas au lieu de bêtement se planter

 

3.

On peut visualiser la requête GET effectuée par un navigateur Web récent à l’aide des outils développeurs. Dans Firefox, on peut les ouvrir à l’aide du raccourci (Ctrl+Maj+I) puis se rendre dans l’onglet « Réseau » et rafraîchir la page. On peut retrouver les lignes de la requête GET envoyée dans la section « En-têtes de la requête » (HTTP headers) dont voici un extrait lorsqu’on visite la page Wikipedia portant sur le protocole HTTP:

GET /wiki/Hypertext_Transfer_Protocol HTTP/1.1
Host: en.wikipedia.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:41.0) Gecko/20100101 
            Firefox/41.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
Cookie: WMF-Last-Access=24-Oct-2015; GeoIP=CH:::47.00:8.00:v4; 
        enwikimwuser-sessionId=9c0945c3c63ca081
Connection: keep-alive
If-Modified-Since: Thu, 22 Oct 2015 09:10:48 GMT
Cache-Control: max-age=0

Les lignes 3 à 12 sont des en-têtes HTTP facultatifs faisant partie de la requête pour donner de plus amples informations au serveur sur le contenu attendu, le type de navigateur effectuant la requête, …
Consigne : Essayer de comprendre chaque en-tête HTTP envoyé par le navigateur en utilisant si nécessaire la documentation du protocole HTTP. Relever les en-têtes qui peuvent être problématiques du point de vue de la protection de la vie privée.