Le routeur dans Forge¶
Ce document explique le routeur HTTP de Forge : déclarer des routes, les regrouper, les nommer et générer leurs URLs avec le module core.http.router.
1. Rôle¶
core.http.router associe une méthode HTTP et un chemin à un gestionnaire, c'est-à-dire l'action d'un contrôleur.
Le routeur compile chaque route en une expression régulière, capture les segments dynamiques (/client/show/{id}), et résout une requête entrante vers le bon gestionnaire.
Il permet aussi de regrouper des routes partageant des réglages communs (préfixe, accès public, protection CSRF, mode API) et de générer une URL depuis le nom d'une route.
La convention de route (ADR-029) est : chemin /<contrôleur>/<méthode> (l'index est le chemin nu), nom <contrôleur>-<méthode>.
2. Vue d'ensemble rapide¶
| Élément | Valeur |
|---|---|
| Module | core.http.router |
| Couche | HTTP |
| Rôle | associer méthode et chemin à un gestionnaire, résoudre les requêtes |
| Classes publiques | Router, RouteGroup, RouteEntry |
| Type lié | Handler (alias Callable[..., Any]) |
| Constantes liées | SAFE_METHODS, UNSAFE_METHODS |
| Exception liée | TypeError (handler non appelable), ValueError (nom de route en double), KeyError (route inconnue ou paramètre manquant dans url_for) |
| Convention | ADR-029 (chemin /contrôleur/méthode, nom contrôleur-méthode) |
3. Schémas UML¶
3.1 Diagramme de classe¶
Le diagramme montre les trois classes du module et leurs relations : le Router détient des RouteEntry, et un RouteGroup ajoute des routes au Router via un préfixe partagé.
classDiagram
direction LR
class Router {
+add(method, pattern, handler, name, public, csrf, api) Router
+group(prefix, public, csrf, api) RouteGroup
+match(method, path) tuple|None
+resolve(method, path) tuple|None
+is_public(path, method) bool
+iter_routes() list~RouteEntry~
+url_for(name, **params) str
}
class RouteGroup {
+str prefix
+add(method, pattern, handler, ...) RouteGroup
}
class RouteEntry {
+str|list method
+str pattern
+Handler handler
+str|None name
+bool public
+bool csrf
+bool api
+matches_method(method) bool
+match(path) dict|None
+requires_csrf(method) bool
+method_label
}
Router "1" o-- "0..*" RouteEntry : détient
Router --> RouteGroup : ouvre via group()
RouteGroup --> Router : ajoute des routes
À retenir :
Routerest le point d'entrée : il enregistre les routes et ouvre des groupes ;- chaque route déclarée devient un
RouteEntrycompilé en expression régulière ; - un
RouteGroupfactorise un préfixe et des réglages, puis délègue àRouter.add.
3.2 Diagramme de séquence¶
Le diagramme montre la résolution d'une requête entrante vers un gestionnaire.
sequenceDiagram
participant Forge as Application Forge
participant Router as Router
participant Entry as RouteEntry
Forge->>Router: resolve("GET", "/client/show/42")
Router->>Router: match(method, path)
loop pour chaque route déclarée
Router->>Entry: matches_method("GET") ?
Router->>Entry: match("/client/show/42") ?
Entry-->>Router: {"id": "42"} ou None
end
Router-->>Forge: (handler, {"id": "42"})
À retenir :
matchparcourt les routes dans l'ordre de déclaration et retourne la première qui correspond ;resolverenvoie le couple(handler, params)prêt à appeler ;- les segments dynamiques capturés sont retournés sous forme de dictionnaire (vide pour une route statique).
4. API publique¶
Router¶
| Élément | Signature | Rôle |
|---|---|---|
add |
add(method: str | list[str], pattern: str, handler: Handler, *, name: str | None = None, public: bool = False, csrf: bool = True, api: bool = False) -> Router |
enregistre une route, retourne self pour le chaînage |
group |
group(prefix: str, *, public: bool = False, csrf: bool = True, api: bool = False) -> RouteGroup |
ouvre un groupe partageant un préfixe et des réglages |
match |
match(method: str, path: str) -> tuple[RouteEntry, dict[str, Any]] | None |
trouve l'entrée correspondante et ses paramètres |
resolve |
resolve(method: str, path: str) -> tuple[Handler, dict[str, Any]] | None |
trouve le gestionnaire et ses paramètres |
is_public |
is_public(path: str, method: str | None = None) -> bool |
indique si le chemin correspond à une route publique |
iter_routes |
iter_routes() -> list[RouteEntry] |
retourne les routes dans l'ordre de déclaration |
url_for |
url_for(name: str, **params: Any) -> str |
génère l'URL d'une route nommée (segments URL-encodés) |
RouteGroup¶
| Élément | Signature | Rôle |
|---|---|---|
add |
add(method: str | list[str], pattern: str, handler: Handler, *, name=None, public=None, csrf=None, api=None) -> RouteGroup |
ajoute une route héritant des réglages du groupe |
Un réglage laissé à None dans RouteGroup.add hérite de la valeur du groupe.
RouteEntry¶
| Élément | Signature | Rôle |
|---|---|---|
RouteEntry |
RouteEntry(method, pattern, handler, *, name=None, public=False, csrf=True, api=False) |
une route compilée (méthode(s), motif, gestionnaire, indicateurs) |
matches_method |
matches_method(method: str) -> bool |
vrai si la route accepte cette méthode |
match |
match(path: str) -> dict[str, Any] | None |
paramètres capturés, ou None si le chemin ne correspond pas |
requires_csrf |
requires_csrf(method: str) -> bool |
vrai si la protection CSRF s'applique pour cette méthode |
method_label |
propriété | libellé lisible de la ou des méthodes |
Signification des indicateurs d'une route :
| Indicateur | Valeur par défaut | Rôle |
|---|---|---|
public |
False |
route accessible sans authentification |
csrf |
True |
protection CSRF exigée sur les méthodes non sûres |
api |
False |
route d'API (réponses JSON, pas de redirection login) |
name |
None |
nom stable de la route (convention contrôleur-méthode) |
Motifs de chemin reconnus :
| Motif | Sens |
|---|---|
/clients |
route statique exacte |
/clients/{id} |
segment dynamique nommé |
/clients/{id}/edit |
segment dynamique en position intermédiaire |
5. Contextes d'utilisation¶
| Besoin | Élément |
|---|---|
| Déclarer les routes de l'application | mvc/routes/, un fichier par contrôleur, par groupes |
| Séparer routes publiques et protégées | router.group(..., public=True) |
| Ajouter les routes d'un opt-in | register_<module>_routes(router) |
| Résoudre une requête vers un gestionnaire | router.resolve(method, path) |
| Générer l'URL d'une route nommée | router.url_for(name, **params) |
6. Exemples d'utilisation¶
Déclarer des routes nommées et un groupe public :
from core.http.router import Router
router = Router()
# Convention ADR-029 : chemin /<contrôleur>/<méthode>, nom <contrôleur>-<méthode>.
router.add("GET", "/", HomeController.index, name="home-index")
router.add("GET", "/client/show/{id}", ClientController.show, name="client-show")
with router.group("", public=True) as public:
public.add("GET", "/login/form", LoginController.form, name="login-form")
public.add("POST", "/login/login", LoginController.login, name="login-login")
Résoudre une requête et générer une URL :
result = router.resolve("GET", "/client/show/42")
# (ClientController.show, {"id": "42"})
url = router.url_for("client-show", id=42)
# "/client/show/42"
Validation à l'enregistrement
Un handler non appelable est rejeté dès add, avec une TypeError qui pointe la ligne fautive de routes.py.
Un nom de route déjà utilisé lève une ValueError, ce qui évite les collisions silencieuses.
URL-encodage des segments
url_for encode chaque paramètre, y compris /, espace, ? ou #.
Un paramètre manquant lève une KeyError listant les segments non résolus.
Voir aussi¶
- L'objet Request dans Forge : la requête routée et ses segments dynamiques.
- Les helpers de réponse dans Forge : produire la réponse d'un gestionnaire.
- Les slugs d'URL dans Forge : produire des segments d'URL lisibles.