TrueAsync Server

(PHP 8.6+, true_async_server 0.6+)

TrueAsync Server est une extension PHP native qui exécute un serveur HTTP performant directement à l'intérieur du processus PHP. Sans démon séparé, sans reverse-proxy, sans pont FastCGI.

Il prend en charge dès l'installation HTTP/1.1 et HTTP/2 sur le même port TCP. Le choix du protocole se fait par négociation ALPN (en TLS) ou via HTTP Upgrade. HTTP/3 fonctionne sur le même port UDP (QUIC) et est annoncé aux clients via l'en-tête Alt-Svc.

WebSocket, SSE et gRPC sont déjà conçus pour le même modèle d'un unique listener avec détection de protocole, mais sont encore en développement (voir Roadmap).

php
use TrueAsync\HttpServer;
use TrueAsync\HttpServerConfig;

$server = new HttpServer(
    (new HttpServerConfig())
        ->addListener('0.0.0.0', 8080)
        ->setWorkers(4)
);

$server->addHttpHandler(function ($request, $response) {
    $response->setStatusCode(200)->setBody('Hello, World!');
});

$server->start();

Pourquoi

L'objectif du serveur est de révéler le potentiel des applications concurrentes en PHP.

TrueAsync a doté le langage de véritables coroutines, d'E/S non bloquantes et de pools de connexions. Pour que ce potentiel se concrétise sous charge production, il faut un serveur conçu dès le départ pour ce modèle : un processus longue durée avec une event-loop, dans laquelle chaque requête reçoit sa propre coroutine et où l'ordonnanceur bascule entre elles à chaque attente d'E/S.

TrueAsync Server est précisément ce serveur. Aucune couche d'indirection entre les coroutines et le réseau : le listener, le parseur de protocole, le dispatcher de requêtes et le handler vivent dans le même processus et dans la même event-loop. Les connexions à la base sont réutilisées via Async\Pool, opcache reste chaud entre les requêtes, le cold-start est payé une seule fois, lors du start().

Fonctionnalités

StatutFonctionnalitéDétails
HTTP/1.1Conformité complète RFC 9112, keep-alive, pipelining (via llhttp, le même parseur que Node.js)
HTTP/2Multiplexage, server push (libnghttp2 ≥ 1.57, floor pour CVE-2023-44487)
HTTP/3 / QUICTransport UDP via libngtcp2 + libnghttp3, API QUIC TLS d'OpenSSL 3.5
TLS 1.2 / 1.3OpenSSL 3.x, négociation ALPN, chiffrements faibles désactivés
Compressiongzip (zlib-ng / zlib), Brotli, zstd : sur la réponse et décodage des corps entrants pour tous les protocoles
Multipart / file uploadsParseur streaming zero-copy
BackpressureCoDel (RFC 8289), pause adaptative de l'accept sous charge
Streaming du corps de requêteOptionnel via HttpRequest::readBody() ; uploads sans conserver le corps en RAM
sendFileService efficace de fichiers disque directement depuis le handler
Worker pool intégrésetWorkers(N) : N threads via Async\ThreadPool + SO_REUSEPORT
Per-request scopeChaque handler dans son propre scope ; Async\request_context() fournit un contexte commun à toute l'arborescence de coroutines de la requête
Coroutines nativesIntégration profonde avec TrueAsync : toute E/S bloquante dans le handler suspend la coroutine, pas le thread
Zero-copyAllocations minimales sur le chemin chaud
📋WebSocketRFC 6455, Upgrade depuis HTTP/1.1 et HTTP/2
📋SSEServer-Sent Events
📋gRPCAu-dessus de HTTP/2, unary et streaming

Architecture : event loop mono-thread

Le même modèle que NGINX, Envoy, Node.js et Rust Tokio/hyper.

Un seul thread est propriétaire de la connexion et de la requête, de l'accept au send. Aucun transfert entre accept-thread et worker-thread, aucun verrou, aucun changement de contexte entre eux. Une event-loop unique accepte la connexion, lit les octets du socket, parse le HTTP, dispatche la requête vers le handler et écrit la réponse, sans quitter le thread.

       ┌─────────────────────────────────────────┐
       │              Event Loop Thread          │
       │                                         │
accept ─►  parse  ─►  dispatch  ─►  respond      │
       │     ▲                        │          │
       │     └──── coroutine yield ◄──┘          │
       └─────────────────────────────────────────┘

Les E/S non bloquantes sont assurées par le reactor libuv (via TrueAsync). Quand une coroutine doit attendre un fichier, une base de données ou la prochaine frame WebSocket, elle rend la main à l'event-loop, qui prend immédiatement le prochain événement prêt. Le thread n'est jamais inactif dans un read()/recv().

Pour le passage à l'échelle multi-cœurs, on lève un multi-worker via setWorkers(N) : l'Async\ThreadPool intégré crée N threads OS, chacun avec son event-loop indépendante, et SO_REUSEPORT (Linux/BSD) laisse le noyau répartir les connexions entrantes entre eux. Aucun shared state, aucun verrou global.

Par où commencer

Référence API

Alternatives

FrankenPHP est un serveur embarquable séparé basé sur Caddy/Go, dans lequel PHP joue le rôle de worker. Il est pratique lorsque vous avez besoin des fonctionnalités de Caddy (Let's Encrypt automatique, configuration via Caddyfile) ou d'une intégration dans une infrastructure Caddy existante. TrueAsync Server est l'alternative native sans runtime Go : le serveur vit directement dans le processus PHP.