Routes



Le serveur "attend la commande du client" et va répondre à sa demande. La création d'un serveur consiste donc à définir les différentes commandes que le serveur va pouvoir honorer, et la manière de le faire.

On peut voir le serveur comme un "restaurant", où le client (le navigateur) va passer une commande (une requête HTTP) et le serveur transmettre la commande à la cuisine (le code python) pour la préparer. Le serveur va ensuite transmettre la réponse au client (le plat préparé).

La demande est faite par le client par l'envoi d'une requête HTTP. L'URL de la requête, la méthode HTTP utilisée (GET, POST, etc.), et les données envoyées (le cas échéant) sont des éléments clés pour déterminer comment le serveur va répondre.

Notion de route

Définir des routes permet d'associer un ou plusieurs méthodes et une ou plusieurs URLs à une fonction python. Le framework va alors utiliser cette fonction pour créer la réponse HTTP à envoyer au client.

Bottle (comme la plupart des autres frameworks) utilise un décorateur pour définir une route. Un décorateur est une fonction qui va modifier le comportement d’une autre fonction. En l’occurrence ici, ce décorateur va ajouter à la fonction hello la capacité à répondre à une requète http précise (lire les en-têtes et les données de la requète, rajouter les codes / en-têtes de la réponse, etc...).

Le décorateur @route utilisé ici effectue la liaison entre une URL et une fonction python, et enregistre cette association dans le routeur global de l'application.

@route('/hello')
def hello():
    return "Hello World!"

La fonction hello() est appelée lorsque le serveur reçoit une requête sur cette URL dont le chemin est exactement /hello et la méthode GET. La fonction doit retourner une chaîne de caractères qui sera envoyée au client dans la réponse HTTP.

Il est possible d'associer plusieurs routes à la même fonction, en spécifiant plusieurs URL dans le décorateur @route :

@route('/hello')
@route('/salut')
def hello():
    return "Hello World!"

Il est aussi possible de spécifier plusieurs méthodes HTTP pour une même route en précisant la liste des méthodes dans le décorateur @route :

@route('/hello', method=['GET', 'POST'])
def hello():
    return "Hello World!"

Des raccourcis existent :

  • @get('/hello') : équivalent à @route('/hello', method=['GET'])
  • @post('/hello') : équivalent à @route('/hello', method=['POST'])
  • @put('/hello') : équivalent à @route('/hello', method=['PUT'])
  • @delete('/hello') : équivalent à @route('/hello', method=['DELETE'])
  • ...

Il faut penser à les importer avant de les utiliser.

from bottle import get, post, put, delete #...

Routes dynamiques

Jokers

Une route dynamique est une route qui permet d'associer plusieurs URLs à une même fonction, en utilisant des "jokers" dans l'URL. Ces jokers sont écris sous la forme <nom> et "capturent" dans un paramètre du même nom la valeur correspondante dans l'URL, jusqu'au prochain slash. Par exemple, la route /hello/<name> va accepter les demandes /hello/alice et /hello/bob, mais pas /hello/, ni /hello, ni /hello/alice/bob.

Le ou les "jokers" sont passés par nom à la fonction python.

@route('/hello/<name>')
def hello(name):
    return f"Hello {name}!"

Plusieurs "jokers" peuvent être utilisés dans la même route : @route('/hello/<action>/<user>')

Filtres

Des filtres peuvent être ajoutés aux jokers pour restreindre les valeurs qui vont "matcher" :

  • :int correspond uniquement aux chiffres (signés) et convertit la valeur en entier.
  • :float similaire à :int mais pour les nombres décimaux.
  • :path correspond à tous les caractères, y compris le caractère slash, et peut être utilisé pour correspondre à plus d'un segment de chemin.
  • :re vous permet de spécifier une expression régulière1 personnalisée dans le champ de configuration. La valeur appariée n'est pas modifiée.

Exemples de filtres :

  • @route('/plus/<a:int>/<b:int>') : le joker a et b doivent être des entiers, et il répondra à la requête /plus/2/3 avec a=2 et b=3 (nombres entiers) mais pas à la requête /plus/2.5/3.5 ni à la requête /plus/toto/titi
  • @route('/plus/<a:float>/<b:float>') : le joker a et b doivent être des flottants, et il répondra à la requête /plus/2.5/3.5 avec a=2.5 et b=3.5 (nombres flottants) mais pas à la requête /plus/toto/3.
  • @route('/file/<name:path>') : le joker name peut contenir des slashes, il répondra à la requête /file/mon/fichier.txt avec name='mon/fichier.txt'
  • @route('/code/<id:re:[A-Z0-9]+>') : le joker id doit être un code ne comportant que des lettres majuscules et des chiffres, et il répondra à la requête /code/ABC123 avec id='ABC123', mais pas à la requête /code/abc123 ou /code/AB.CD
1

Une expression régulière (ou expression rationnelle ou expression normale ou motif) est une chaîne de caractères qui décrit, selon une syntaxe précise, un ensemble de chaînes de caractères possibles. Voir fr.wikipedia.org/wiki/Expression_régulière et https://docs.python.org/fr/3/howto/regex.html pour plus de détails. https://regex101.com/ est un site très pratique pour tester les expressions régulières.

En pratique

Bonjour personnalisé

Dans notre serveur, rajouter une route de bonjour personnalisé qui répond à une requète du type /hello/bob en renvoyant un message du type "Hello bob !"

Correction

Télécharger le code

# Bonjour personnalisé
@route('/hello/<name>')
def hello_user(name):
    return f"Hello {name} !"

Calcul

Ajouter aussi une route de calcul qui répond à une requète du type /calcul/add/3/2.5 et qui renvoie le résultat sous la forme "resultat = 5.5" (on utilisera "add", "sub", "mul" et "div" pour addition, soustraction, multiplication et division), l'opération étant 1er arg opérateur 2ème arg (ex : 3 add 2.5 = 3 + 2.5 = 5.5). Si l'opérateur n'est pas reconnu, renvoyer "opérateur inconnu".

Correction

Télécharger le code

# Calcul
@route('/calcul/<op>/<a:float>/<b:float>')
def calcul(op, a, b):
    match op:
        case 'add':
            return f"resultat = {a + b}"
        case 'sub':
            return f"resultat = {a - b}"
        case 'mul':
            return f"resultat = {a * b}"
        case 'div':
            return f"resultat = {a / b}"
        case _:
            return "opérateur inconnu"

Programme

  • NSI 1ère : intéraction client/serveur, requêtes HTTP, réponses du serveur