Spec couche 2 — air-http (HTTP/1.1 + HTTP/2 + HTTP/3, patron sans-IO)
Spécification technique — Version 0.1 (proposition). Couche 2 « Frameworks système ».
Cadrage.
air-httpimplémente HTTP/1.1 (RFC 9112), HTTP/2 (RFC 9113) et HTTP/3 (RFC 9114) — h1/h2/h3 en modules internes d’un crate unique (note réseau §4 : «air-http(h1/h2/h3 = modules internes) »), + WebSocket (RFC 6455/8441). Maison, pur Rust, zéro-C, zérounsafe; s’appuie surair-tls(h1/h2),air-quic(h3),air-network(connexion). Suit le patron sans-IO 9-composants par version.
Position et méthode
HTTP est un protocole applicatif filaire (cœur sans-IO + pilote I/O mince). Chaque
version a son cœur ; le crate expose une surface unifiée (Request/Response/
Body) au-dessus, la version étant négociée (ALPN via air-tls/air-quic).
Empilement (note §2.5) :
air-http (h3) → air-quic → air-socket (UDP)
air-http (h2) → air-tls 1.3 → air-socket (TCP)
air-http (h1) → air-tls / air-socket (TCP)
└── surface unifiée Request/Response/Body ; version choisie par ALPN
Décision — crate unique, versions internes. h1/h2/h3 partagent le modèle de message (
Request/Response/HeaderMap/Body) ; les différences (framing, compression d’en-têtes, multiplexage) sont internes. Un consommateur écrit une requête une fois, la version est un détail négocié. (vs 3 crates séparés → API fragmentée.)
Périmètre de sécurité — moderne, borné
- h1 :
Content-Length/chunked stricts (anti request-smuggling : rejet des cadrages ambigus, une seule interprétation) ; pas de HTTP/0.9 ; en-têtes bornés. - h2/h3 : compression d’en-têtes HPACK (RFC 7541) / QPACK (RFC 9204) avec tables bornées (anti HPACK-bomb / CVE-2016-6581-class) ; limites de streams, de frames (anti Rapid-Reset CVE-2023-44487 : plafond de streams annulés/RTT).
- TLS obligatoire pour h2/h3 (hérité
air-tls/air-quic) ; h1 en clair explicite. - Bornes anti-abus : taille d’en-têtes, nombre de headers, profondeur, débit de frames de contrôle — tout borné (surface hostile distante).
Dépendances
- Tierces externes : ZÉRO hors l’exception TLS déléguée (
air-tls, ADR-042). - Crates Air :
air-network(L2, connexion/ALPN),air-tls(L2, h1/h2),air-quic(L2, h3),air-runtime(L1, async),air-base-core(L1, erreurs/durées). (HTTP n’a pas de crypto propre.) - Zéro
unsafeexposé ; parsing défensif (&[u8]vs&str,get(), longueurs bornées).
Anatomie : les 9 composants par version
| # | Composant | h1 | h2 | h3 |
|---|---|---|---|---|
| 1 | Framer | H1Framer (start-line + headers + body chunked/CL) | H2FrameFramer (len/type/flags/streamid) | sur streams QUIC (framing délégué à air-quic) |
| 2 | Codec | H1Codec (champs d’en-tête) | H2FrameCodec + Hpack | H3FrameCodec + Qpack |
| 3 | StateMachine | requête/réponse + keep-alive | connexion + états de stream (RFC 9113 §5.1) | idem sur streams QUIC |
| 4 | Handshaker | // délégué : TLS via air-tls, version via ALPN | idem (+ SETTINGS échange) | // délégué : QUIC/TLS via air-quic |
| 5 | Flow Controller | // NO : TCP | H2FlowController (WINDOW_UPDATE, conn+stream) | // délégué : flow control QUIC |
| 6 | Multiplexer | // NO : 1 requête/connexion (keep-alive séquentiel) | H2StreamMux (streams sur 1 TCP) | H3StreamMux (streams QUIC) |
| 7 | Timer Manager | timeouts requête/keep-alive | + PING/keepalive, rapid-reset guard | idem |
| 8 | Session Context | connexion (keep-alive) | SETTINGS + table dynamique HPACK | SETTINGS + table dynamique QPACK |
| 9 | Extension hooks | Upgrade (WebSocket h1) | extended CONNECT (WebSocket h2, RFC 8441) | extended CONNECT (h3) |
Inventaire des objets — ~33 objets
A. Surface unifiée publique — ~14
| # | Objet | Rôle |
|---|---|---|
| 1 | HttpClient (+ HttpClientConfig/Builder) | client haut niveau (pool, ALPN, redirections) |
| 2 | HttpServer (+ HttpServerConfig/Builder) | serveur (handler par requête) |
| 3 | Request (+ RequestBuilder) | requête (méthode, URI, headers, body) |
| 4 | Response (+ ResponseBuilder) | réponse (statut, headers, body) |
| 5 | HeaderMap | multimap d’en-têtes (ordre préservé, casse-insensible) |
| 6 | HeaderName / HeaderValue | newtypes validés (HeaderValue = bytes, pas UTF-8 présumé) |
| 7 | Method | enum (GET/POST/… + extension) |
| 8 | StatusCode | newtype u16 validé (1xx–5xx) |
| 9 | HttpVersion | enum Http11/Http2/Http3 |
| 10 | Uri | URI parsé/validé (scheme/authority/path/query) |
| 11 | Body / BodyStream | corps en flux (back-pressure, ADR-028 buffers possédés) |
| 12 | HttpConnection | connexion négociée (sur air-network) — version transparente |
| 13 | WebSocket (+ WebSocketMessage) | RFC 6455/8441 (module, voir ci-dessous) |
| 14 | HttpError | enum (ADR-019) — protocole, transport, TLS |
B. Cœurs sans-IO internes — ~15
- Partagé :
HeaderCodec,BodyReader/ChunkedCodec,UriCodec. - h1 :
H1Framer,H1Codec,H1StateMachine. - h2 :
H2FrameCodec,Hpack(encoder+decoder, table dynamique bornée),H2StreamMux,H2FlowController,H2StateMachine. - h3 :
H3FrameCodec,Qpack(encoder+decoder),H3StateMachine.
C. Enums/valeurs wire — ~4
H2FrameType, H3FrameType, SettingId, WebSocketOpcode.
Codecs (bytes ↔ messages)
#![allow(unused)]
fn main() {
/// Trait de codec commun (borné, anti-panic) — décliné h1/h2/h3.
pub trait HttpCodec { /* encode(&self, &mut Vec<u8>) ; decode(&mut Reader) -> Result<…> */ }
// h1 : start-line + en-têtes CRLF + corps (chunked | content-length).
// REJET des cadrages ambigus (CL + TE, doubles CL) → HttpError (anti-smuggling).
// h2 : HPACK (RFC 7541) — table dynamique BORNÉE (SETTINGS_HEADER_TABLE_SIZE),
// décodage d'entiers/chaînes Huffman borné (anti-bomb).
// h3 : QPACK (RFC 9204) — table dynamique + flux d'encodeur/décodeur, bornés.
}
HPACK/QPACK = la surface hostile n°1 (compression d’en-têtes venant du pair) → fuzz obligatoire, tables bornées, décompression jamais non bornée.
Machine à états — multi-thread (mono-propriétaire, sans-IO)
#![allow(unused)]
fn main() {
impl HttpClient {
pub async fn send(&self, request: Request) -> Result<Response, HttpError>; // version transparente
pub async fn connect(&self, uri: &Uri) -> Result<HttpConnection, HttpError>; // ALPN choisit h1/h2/h3
}
// Cœur sans-IO par version : feed(&[u8]) -> events ; poll_transmit() -> &[u8] ;
// handle_timeout(now). Pilote I/O = air-network (connexion) + air-runtime (async).
}
Thread model : HttpConnection Send/!Sync (une connexion, une tâche ; le
multiplexage h2/h3 se fait dans la connexion, pas entre threads) ; HttpClient
partageable (Arc, pool de connexions) ; configs Arc Send/Sync. Thread-per-core
(ADR-038/039).
WebSocket (module, RFC 6455 + 8441)
#![allow(unused)]
fn main() {
impl WebSocket {
/// Sur h1 : Upgrade (RFC 6455). Sur h2/h3 : extended CONNECT (RFC 8441).
pub async fn connect(uri: &Uri, params: &HttpClientConfig) -> Result<Self, HttpError>;
pub async fn send(&mut self, msg: WebSocketMessage) -> Result<(), HttpError>; // Text/Binary/Ping/Close
pub async fn receive(&mut self) -> Result<WebSocketMessage, HttpError>;
}
// Framing WS : masquage client obligatoire, bornes de frame, close handshake propre.
}
Stratégie de tests
- Couverture couche 2 > 90 % (viser 100 % sur les codecs sans-IO).
- Par composant : Framer/Codec fuzzés (h1 smuggling, HPACK/QPACK bombs, frames h2 malformées) ; StateMachine property/model-based (états de stream) ; FlowController property (fenêtre h2) ; rapid-reset guard (plafond streams annulés/RTT).
- Conformité : suite h2spec/h3spec, vecteurs HPACK (RFC 7541 annexe) / QPACK ; interop vs curl/nginx/quiche.
- Pilote I/O : loopback réel via
air-network(h1/h2 sur TLS, h3 sur QUIC).
Récapitulatif & décisions
| Famille | Objets |
|---|---|
| A. Surface unifiée | ~14 |
| B. Cœurs sans-IO (h1/h2/h3 + partagé) | ~15 |
| C. Enums wire | ~4 |
| Total | ~33 |
- Crate unique, h1/h2/h3 modules internes ; surface unifiée
Request/Response/Body; version par ALPN. - Moderne, borné : anti-smuggling (h1), HPACK/QPACK bornés, anti rapid-reset ; TLS obligatoire h2/h3.
- Zéro-C, zéro
unsafe; composeair-tls/air-quic/air-network. - WebSocket intégré (RFC 6455 h1 + 8441 h2/h3).
- Async 1ʳᵉ classe (ADR-038), connexion
Send/!Sync, poolArc.
Travail à reprendre
- Priorité d’implémentation : h1 d’abord (sur
air-tls/air-socket), puis h2, puis h3 (attendair-quic). - Consommateur : l’équivalent
URLSession(client URL-loading haut niveau) — couche à confirmer (couche 4 élargie ?), cf. noteapi-reseau-strategie. - Cookies, cache, redirections, auth (couche cliente haut niveau) ; h2 Server Push (déprécié → omis) ; ABI C différée.
Licence du document : MPL 2.0
Statut : Spécification air-http (couche 2) v0.1 — HTTP/1.1+2+3 pur Rust, crate
unique, patron sans-IO ; compose air-tls/air-quic/air-network. Implémentation à
suivre (h1 d’abord).