Introduction

Un programme doit pouvoir interagir avec des ressources en réseau. Le composant bas niveau utilisé à cet effet est la socket ; il permet d’établir de programmer clients et serveurs TCP ou TCP.

La bibliothèque standard Python comprend des composants de plus haut niveau pour — par exemple — effectuer des requêtes HTTP. Ce cours introduit les requêtes HTTP GET.

Requête HTTP

Une requête HTTP comprend deux parties :

  • les entêtes :
    • méthode (GET, POST…) ;
    • URL ;
    • format souhaité pour la réponse : application/xml, application/json ou text/html ;
    • le format des données envoyées (le cas échéant) : application/xml, application/json ou application/x-www-form-urlencoded (utilisé lors de la soumission d’un formulaire HTML) ;
  • le corps de la requête (les données envoyées) — uniquement pour un POST ou un PUT.

La réponse comporte également des entêtes et un corps.

Plusieurs exceptions peuvent survenir lors d’une requette HTTP :

  • serveur non joignable (problème réseau, DNS…)
  • erreur dans l’URL ou les paramètres de la requête HTTP.

Fonction utilitaire

La fonction suivante peut-être utilisée pour effectuer des requêtes HTTP. Elle est pensée pour pouvoir interagir avec des services web.

import sys
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
from typing import Optional

def http_request (url: str, method: str = "GET",
                  accept: Optional[str] = None,
                  data_in: Optional[bytes] = None,
                  content_type: Optional[str] = None) -> Optional[bytes]:
    """
    Enveloppe pour simplifier les requêtes HTTP(S).
    @param url l'URL de la ressource
    @param method la méthode HTTP (GET, POST, PUT, DELETE…)
    @param accept le format de la réponse souhaité (text/html,
                  application/xml, application/json)
    @param data_in les donnes à envoyer au serveur
    @param content_type le format des données envoyées
                        (application/xml, application/json,
                        application/x-www-form-urlencoded)
    @return la réponse du serveur
    """
    
    data_out: Optional[bytes] = None
    
    try:
        headers = {"User-Agent": "Python"}
        if accept is not None:
            headers["Accept"] = accept
        if content_type is not None:
            headers["Content-Type"] = content_type
        
        req = Request(url, data=data_in, headers=headers, method=method.upper())
        with urlopen(req) as response:
            data_out = response.read()

    except HTTPError as e:
        print(f"erreur : statut HTTP non OK\n{e.code}: {e.reason}", file=sys.stderr)
    except URLError as e:
        print(f"erreur : connexion impossible\n{e.reason}", file=sys.stderr)
        
    return data_out

Octets et chaînes de caractères

Les données envoyées ou reçues via le protocole HTTP sont de type bytes (octets) qui doivent être encodées et décodées depuis et vers str, par exemple en UTF-8.

Encodage str → bytes

try:
    octets = texte.encode('utf-8')
except UnicodeEncodeError as e:
    print(f"erreur d'encodage UTF-8\n{e}", file=sys.stderr)

Décodage bytes → str

try:
    texte = octets.decode('utf-8')
except UnicodeDecodeError as e:
    print(f"erreur de décodage UTF-8\n{e}", file=sys.stderr)

Exemple d’utilisation

Cet exemple utilise la fonction http_request pour effectuer une recherche sur Wikipedia.

import json
from urllib.request import quote
from typing import Optional, List

def wikipedia_search (search: str) -> Optional[List[str]]:
    """
    Recherche sur Wikipédia - cf https://www.mediawiki.org/wiki/API:REST_API
    @param search la recherche
    @return: liste des resultats (titres) ou None si erreur reseau/API
    """
    
    results = None
    search = quote(search) #encodage des termes de la recherche
    api = "https://fr.wikipedia.org/w/rest.php/v1/"
    url = api + "search/page?limit=10&q=" + search
    
    response_bytes = http_request(url)
    if response_bytes is not None:
        try:
            response_str = response_bytes.decode("utf-8")
            data = json.loads(response_str)
            #print(json.dumps(data, indent=4)) #pour déboguer
            results = []
            for r in data["pages"]:
                results.append(r["title"])
        except UnicodeDecodeError as e:
            print(f"erreur : encodage UTF-8 invalide\n{e}", file=sys.stderr)
        except json.JSONDecodeError as e:
            print(f"erreur : format JSON invalide\n{e}", file=sys.stderr)
        
    return results


if __name__ == "__main__":
    search = input("Recherche : ")
    titles = wikipedia_search(search)
    if titles is not None:
        for t in titles:
            print(t)

Remarque : la recherche peut comporter des caractères spéciaux (comme l’espace) qui doivent être remplacés par %xy…, ou xy… est la valeur hexadécimale du code UTF-8 du caractère (exemple : %20 pour l’espace) ; c’est ce que fait la fonction urllib.request.quote.