Configuration de TrueAsync Server
(PHP 8.6+, true_async_server 0.6+)
Toute la configuration du serveur se fait via l'objet TrueAsync\HttpServerConfig avant l'appel de new HttpServer($config). Une fois HttpServer créé, la configuration est gelée : tout setter sur celle-ci lèvera HttpServerRuntimeException.
use TrueAsync\HttpServer;
use TrueAsync\HttpServerConfig;
use TrueAsync\LogSeverity;
$config = (new HttpServerConfig())
->addListener('0.0.0.0', 8080)
->addListener('0.0.0.0', 8443, tls: true)
->addHttp3Listener('0.0.0.0', 8443)
->setCertificate('/etc/tls/server.crt')
->setPrivateKey('/etc/tls/server.key')
->setWorkers(4)
->setKeepAliveTimeout(60)
->setMaxBodySize(50 * 1024 * 1024)
->setCompressionEnabled(true)
->setLogSeverity(LogSeverity::INFO)
->setLogStream(STDERR);
$server = new HttpServer($config);Les setters renvoient static, donc la configuration se construit en chaîne.
Listeners
Le serveur peut écouter un nombre arbitraire de sockets TCP/Unix et de ports UDP (pour HTTP/3) simultanément.
| Méthode | Ce qu'elle fait |
|---|---|
addListener($host, $port, $tls = false) | TCP, HTTP/1.1 + HTTP/2 (h2c via preface en plaintext, h2 via ALPN en TLS) |
addHttp1Listener($host, $port, $tls = false) | TCP, uniquement HTTP/1.1. Un client avec preface HTTP/2 reçoit un 400 |
addHttp2Listener($host, $port, $tls = false) | TCP, uniquement HTTP/2. Sans TLS, c'est du h2c avec preface obligatoire |
addHttp3Listener($host, $port) | UDP, HTTP/3 / QUIC. TLS 1.3 activé automatiquement, le certificat du serveur est utilisé |
addUnixListener($path) | Socket Unix, HTTP/1.1 + HTTP/2 (style h2c) |
$config
->addListener('0.0.0.0', 80) // H1 + H2c
->addListener('0.0.0.0', 443, tls: true) // H1 + H2 over TLS
->addHttp3Listener('0.0.0.0', 443); // H3 / QUIC sur le même portPour un rollout progressif de HTTP/3, on peut désactiver temporairement l'annonce Alt-Svc :
$config->setHttp3AltSvcEnabled(false);TLS
$config
->setCertificate('/etc/tls/server.crt')
->setPrivateKey('/etc/tls/server.key');Le certificat et la clé sont communs à tous les listeners TLS (y compris HTTP/3). TLS 1.2/1.3, ALPN, chiffrements faibles désactivés, stateless session tickets, safe renegotiation désactivée.
Workers et bootloader
setWorkers(1) (valeur par défaut) active le mode mono-thread : start() fait tourner l'event-loop sur le thread appelant.
setWorkers(N > 1) lève un pool intégré de N threads via Async\ThreadPool. Chaque worker re-bind les mêmes listeners, le noyau (Linux/BSD) répartit l'accept via SO_REUSEPORT. Le start() parent attend la fin de tous les workers.
$config
->setWorkers(4)
->setBootloader(function () {
// exécuté une seule fois dans chaque worker avant la task-loop
require __DIR__ . '/vendor/autoload.php';
Database::warmupPool();
OpcacheWarm::compile();
});Détails : Multi-worker.
Timeouts
| Méthode | Défaut | Ce qui est en timeout |
|---|---|---|
setReadTimeout($sec) | — | réception complète de la requête |
setWriteTimeout($sec) | — | envoi de la réponse |
setKeepAliveTimeout($sec) | — | idle entre requêtes ; 0 désactive keep-alive |
setShutdownTimeout($sec) | — | graceful shutdown : combien de temps attendre les requêtes actives |
Limites et backpressure
$config
->setBacklog(1024)
->setMaxConnections(50_000)
->setMaxInflightRequests(10_000)
->setMaxBodySize(10 * 1024 * 1024)
->setBackpressureTargetMs(10);setMaxConnections($n): limite stricte du nombre de connexions TCP.0lève la limite.setMaxInflightRequests($n): admission control. Au-delà de ce nombre de handlers actifs, les nouvelles requêtes reçoivent un refus rapide. H1 → 503 +Retry-After: 1, H2 →RST_STREAM REFUSED_STREAM(retry-safe selon RFC 7540 §8.1.4). En H2, la limite stricte sur les connexions ne suffit pas, parce que de nouveaux streams arrivent sur une connexion déjà acceptée.0prend la valeurmax_connections × 10.setMaxBodySize($bytes): maximum du corps de requête. Par défaut 10 MiB, plage 1 KiB..16 GiB. H1 renvoie 413 et ferme la connexion ; H2 envoieRST_STREAM(INTERNAL_ERROR).setBackpressureTargetMs($ms): seuil de sojourn CoDel pour le backpressure côté accept. Quand le queue-wait par requête reste au-dessus du seuil pendant 100 ms consécutives, le socket listen est mis en pause.0désactive CoDel. Par défaut 5 ms ; pour un web typique 10–20 ms ; pour des handlers lents (BD, IO) 50–100 ms.
Graceful drain (Step 8)
Gestion de la migration de charge derrière un load balancer L4 :
| Méthode | Défaut | Rôle |
|---|---|---|
setMaxConnectionAgeMs($ms) | 0 (off) | Après une limite avec ±10 % de jitter, la connexion reçoit Connection: close (H1) ou GOAWAY (H2). Équivalent du MAX_CONNECTION_AGE gRPC. Production : 600_000 (10 min). |
setMaxConnectionAgeGraceMs($ms) | 0 | Hard-close après Connection: close/GOAWAY. 0 désactive le timer force-close. |
setDrainSpreadMs($ms) | 5000 | Fenêtre d'étalement uniforme du drain par connexion lors d'un trip CoDel / hard-cap (anti thundering herd). |
setDrainCooldownMs($ms) | 10_000 | Gap minimum entre déclenchements réactifs de drain. |
Limites du streaming HTTP/2
$config
->setStreamWriteBufferBytes(256 * 1024) // 256 KiB par stream, 4 KiB .. 64 MiB
->setH2StaticBudgetMax(0); // 0 = auto (memory_limit / 8)HttpResponse::send($chunk) ne bloque la coroutine du handler que sous backpressure : quand le staging buffer par stream est plein. Par défaut 256 KiB (à titre de comparaison : gRPC-Go 64 KiB, Envoy 1 MiB, Node.js 16 KiB).
Réglages production HTTP/3
$config
->setHttp3IdleTimeoutMs(30_000) // RFC 9000 §10.1
->setHttp3StreamWindowBytes(256 * 1024) // flow control par stream
->setHttp3MaxConcurrentStreams(100) // initial_max_streams_bidi
->setHttp3PeerConnectionBudget(16) // cap par IP source, protection slow-loris
->setHttp3AltSvcEnabled(true); // annonce Alt-Svc RFC 7838Le initial_max_data au niveau connexion est dérivé comme window × max_concurrent_streams (pattern nginx).
Body streaming
Active le streaming pull-based du corps de requête (issue #26) : les parseurs H1/H2 déposent les chunks dans une file, le handler les lit via HttpRequest::readBody() sans retenir l'intégralité du corps en RAM.
$config->setBodyStreamingEnabled(true);
$server->addHttpHandler(function ($req, $res) {
while (($chunk = $req->readBody()) !== null) {
// traiter le chunk (par ex. écriture streaming sur disque, parsing)
}
$res->setStatusCode(204);
});Sans setBodyStreamingEnabled(true), le handler reçoit un corps déjà entièrement lu via getBody() ; readBody() est indisponible dans ce mode.
Comparaison pour 50 POST parallèles de 20 MiB (h2load, WSL2) : peak RSS chute de 1170 MiB à 197 MiB (×6), débit 36 req/s → 100 req/s (×2.7), parce que le dispatch du handler n'attend plus le corps complet.
Voir aussi Streaming.
Auto-await body
$config->setAutoAwaitBody(true); // défaut : trueLorsqu'activé, les requêtes non multipart attendent le corps complet avant l'appel du handler (multipart est toujours en streaming). Utile pour le traitement classique du corps entier.
JSON
$config->setJsonEncodeFlags(JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);Ces drapeaux s'appliquent à HttpResponse::json() quand l'appelant n'a pas passé $flags explicitement. JSON_THROW_ON_ERROR est silencieusement retiré : une erreur d'encodage donne 500 avec un corps JSON d'erreur, l'exception n'est pas propagée dans le handler.
Logging
use TrueAsync\LogSeverity;
$config
->setLogSeverity(LogSeverity::INFO)
->setLogStream(STDERR); // tout php_stream : fichier, php://stderr, php://memory, user wrapperLe logger est désactivé par défaut (LogSeverity::OFF). La severity est figée au démarrage, les changements runtime ne sont pas supportés (modèle mono-thread lock-free).
Niveaux (SeverityNumber OpenTelemetry) :
| Niveau | Ce qui est journalisé |
|---|---|
OFF (0) | rien |
DEBUG (5) | tracing des paquets H3 et autres |
INFO (9) | lifecycle serveur (start/stop), bind retries |
WARN (13) | échec de handshake TLS, peer reset, exceptions absorbées |
ERROR (17) | listener bind failed, erreurs protocole graves |
FATAL est volontairement absent : il passe par zend_error_noreturn(E_ERROR), qui interrompt déjà le processus.
Télémétrie (W3C Trace Context)
$config->setTelemetryEnabled(true);Lorsqu'activé, les traceparent / tracestate entrants sont parsés et attachés à la requête. Dans le handler, sont disponibles :
$req->getTraceParent(); // header brut
$req->getTraceState();
$req->getTraceId(); // 32 caractères hex minuscule
$req->getSpanId(); // 16 caractères hex minuscule
$req->getTraceFlags(); // int (0x01 = sampled)Référence complète
Voir TrueAsync\HttpServerConfig : les 60+ méthodes avec description détaillée et plages de valeurs valides.