Cette page décrit ce que Chappe fait quand une requête arrive : comment la connexion est acceptée, comment le préambule TCP/TLS est inspecté, comment HTTP/1.1 et HTTP/2 sont parsés, comment le Router est invoqué, comment la réponse est écrite. Aucun chiffre de performance ici — voir lien:https://codeberg.org/Vidocq/chappe/blob/main/BENCH.md[BENCH.md].

Architecture en couches

Diagram
  • chappe-api n’a aucune dépendance interne — elle expose les interfaces.

  • chappe-http ne dépend que de chappe-api.

  • chappe-core orchestre et fournit l’implémentation ServerProvider via ServiceLoader.

Boucle d’acceptation

  1. ChappeServer#start() ouvre un ServerSocketChannel (mode bloquant).

  2. Un virtual thread (« acceptor ») boucle sur accept().

  3. Pour chaque socket, un nouveau virtual thread est lancé via Executors.newVirtualThreadPerTaskExecutor().

  4. Le thread inspecte le préambule (TLS ClientHello ou requête HTTP/1.1 cleartext) puis dispatch vers HttpConnection ou Http2Connection.

Pas de Selector applicatif. La VM gère le multiplexage des virtual threads sur les carrier threads plateforme.

Détection de protocole

Diagram

Pipeline HTTP/1.1 — séquence requête

Diagram

Composants impliqués (io.vidocq.chappe.http) :

  • HttpRequestParser — parse request line + headers en zero-allocation sur le hot path (réutilise les buffers du ByteBufferPool).

  • ChunkedInputStream / FixedLengthInputStream — lecture body lazy.

  • HttpResponseWriter — écriture coalescée (status line + headers + premier chunk en un seul write).

  • ArrayHeaders — stockage compact, lookup case-insensitive sans allocation.

Pipeline HTTP/2 — séquence requête

Diagram

Implémentation (io.vidocq.chappe.http.h2) :

  • Http2FrameReader / Http2FrameWriter — framing RFC 9113.

  • HpackEncoder / HpackDecoder — RFC 7541 (table statique 61 entrées, table dynamique paramétrée par SETTINGS, Huffman pour les valeurs courtes).

  • HpackHuffman — tables d’encodage/décodage générées statiquement.

  • Http2SettingsHEADER_TABLE_SIZE, MAX_CONCURRENT_STREAMS, INITIAL_WINDOW_SIZE, MAX_FRAME_SIZE, MAX_HEADER_LIST_SIZE.

  • Http2Stream — état FSM (idle → open → half-closed local/remote → closed).

  • Http2ConnectionException / Http2ErrorCode — gestion GOAWAY / RST_STREAM.

Pipeline HTTP/3 — esquisse

HTTP/3 n’est pas encore actif. Le pipeline cible :

Diagram

Modèle threading

Diagram

Points de design :

  • Aucun thread plateforme dédié à Chappe — uniquement des virtual threads.

  • Pas de pinning — pas de synchronized sur le hot path, pas de JNI bloquant. RequestContext utilise ScopedValue, pas ThreadLocal.

  • Body lazy — la lecture du body n’est faite qu’à l’appel de req.body().asInputStream(). Si le handler retourne sans consommer, le body est drainé proprement avant la requête suivante (keep-alive HTTP/1.1) ou le reset du stream (HTTP/2).

Optimisations zéro-allocation sur le hot path

Documentées dans chappe/OPTIMS.md :

  • Write coalescing — status line + headers + premier chunk émis dans un seul write().

  • Zero-alloc headersArrayHeaders réutilise un buffer scratch, pas de HashMap sur le hot path.

  • Thread-local buffer poolByteBufferPool partage des ByteBuffer direct entre virtual threads via les carriers.

  • Fast path 200 OK — status line HTTP/1.1 200 OK pré-encodé, copie directe.

  • StatusCodeCacheStatusCode enum cachée en String[] indexé.

Pour les chiffres mesurés, voir lien:https://codeberg.org/Vidocq/chappe/blob/main/BENCH.md[BENCH.md] et chappe/BENCHMARKS.md.

Static index O(1)

StaticFileHandler charge un index META-INF/chappe-static-index.properties (généré par chappe-static-index-maven-plugin au build). Lookup O(1) par chemin, pas de scan filesystem au runtime. Les sidecars .gz (et .br si fournis) sont signalés dans l’index — preferPrecompressed(true) les sert zero-copy quand Accept-Encoding les inclut.