Cette page définit le vocabulaire Chappe. Les noms sont alignés sur les RFC HTTP (9110/9112/9113/9114) et sur les API Java 25 modernes (ScopedValue, virtual threads). Pas de jargon maison — un développeur HTTP normal doit reconnaître chaque terme.
Server, Router, Handler
Trois interfaces tiennent toute l’API publique.
| Type | Rôle |
|---|---|
|
Cycle de vie : |
|
Dispatcher de requêtes par méthode + path. Builder fluide. Supporte routes exactes, path params ( |
|
Interface fonctionnelle : |
Request et Response
Les deux types sont immutables côté API. Response se construit via builder ; Request est fourni par le moteur.
// Request — accès en lecture
req.method() // HttpMethod : GET, POST, ...
req.path() // String relatif au mount, sans query string
req.queryParams() // Map<String, List<String>>
req.pathParams() // Map<String, String>
req.headers() // Headers (case-insensitive)
req.body() // Body (asInputStream(), asBytes(), ...)
req.contextPath() // préfixe du mount, "" sinon
req.pathInfo() // alias de path() pour les mounts
req.remoteAddress() // SocketAddress du client
req.isSecure() // true si TLS
req.scheme() // "http" ou "https"
req.attribute(k, v) // attributs mutables per-request (compat Servlet)
Body
Le Body est une abstraction sur le corps de requête ou de réponse. Il existe plusieurs implémentations selon le besoin, toutes scellées dans chappe-api :
| Type | Usage |
|---|---|
|
204, 304, requêtes sans corps. |
|
Réponses courtes (texte, JSON inline). Allocation unique. |
|
Wrappe un |
|
Streaming par push : |
|
Zero-copy via |
Filtres
Un Filter est un middleware déclaratif. Il transforme un Handler en un autre Handler :
Filter logging = next -> req -> {
System.out.println(req.method() + " " + req.path());
return next.handle(req);
};
Helpers fournis par Filter :
-
Filter.addHeader(name, value)— header sur chaque réponse. -
Filter.addHeaderIf(predicate, name, value)— header conditionnel (prédicat évalué per-request). -
Filter.addHeaderIfEnv(envVar, expected, name, value)— gate sur variable d’environnement (typique : staging). -
Filter.gzip()/Filter.gzip(threshold)— compression à la volée négociée viaAccept-Encoding.
Connexion, frame, multiplexage
Vocabulaire HTTP/2 (RFC 9113), réutilisé par HTTP/3 (RFC 9114) avec un transport différent.
| Terme | Sens dans Chappe |
|---|---|
Connexion |
Une socket TCP (HTTP/1.1, HTTP/2) ou un QUIC stream-set (HTTP/3). Un virtual thread par connexion. |
Frame |
Unité de transport HTTP/2 : |
Stream |
Sous-flux logique multiplexé sur une connexion HTTP/2 — une requête + une réponse. |
Multiplexage |
Plusieurs streams en parallèle sur une connexion. HTTP/1.1 ne le permet pas (pipelining ≠ multiplexage) ; HTTP/2 et HTTP/3 oui. |
HPACK |
Compression de headers HTTP/2 (RFC 7541) — table statique + table dynamique + Huffman. Implémenté dans |
Flow control |
Fenêtre de bytes par stream et par connexion (HTTP/2) ; émission de |
Virtual thread per request
Chappe applique une règle stricte : un thread virtuel par connexion. Pas de pool plateforme, pas d'`EventLoopGroup`, pas de Selector côté application.
-
Executors.newVirtualThreadPerTaskExecutor()est le seul exécuteur utilisé. -
La VM Java 25 multiplexe les virtual threads sur quelques carrier threads plateforme.
-
Le code applicatif écrit du synchrone bloquant :
body.asInputStream().readAllBytes()est correct, jamais une régression de scalabilité. -
Pas de
ThreadLocal— voirRequestContextci-dessous.
RequestContext et Scoped Values
RequestContext.CURRENT est un ScopedValue<RequestContext> (JEP 506, finalisé en Java 25). Il porte les attributs de requête, le tenant, l’utilisateur authentifié — tout ce que ThreadLocal portait dans les serveurs traditionnels, en plus propre :
-
propagation explicite via
ScopedValue.where(…).run(…); -
nettoyage automatique en sortie de scope ;
-
compatibilité virtual threads sans risque de pinning.
// Lecture depuis n'importe quel point de la chaîne d'appel
Request req = RequestContext.currentRequest();
Mount, contextPath, pathInfo
Router#mount(prefix, handler) délègue un préfixe entier de chemin, tous verbes confondus. Le préfixe est strippé avant l’appel :
-
request.path()→ relatif au mount (/dashboard), -
request.contextPath()→ le préfixe (/admin), -
request.pathInfo()→ alias depath()pour les mounts.
Ordre de résolution au sein d’un Router :
-
routes exactes (premier match par path + method),
-
405 Method Not Allowedsi le path matche mais pas la méthode (headerAllowinjecté), -
mounts (premier préfixe match, ordre d’enregistrement),
-
notFound(fallback404).