TrueAsync Server
(PHP 8.6+, true_async_server 0.6+)
TrueAsync Server è un'estensione PHP nativa che esegue un server HTTP performante direttamente all'interno del processo PHP. Niente daemon separato, niente reverse proxy, niente ponte FastCGI.
Supporta da subito HTTP/1.1 e HTTP/2 sulla stessa porta TCP. La scelta del protocollo avviene tramite negoziazione ALPN (per TLS) o tramite HTTP Upgrade. HTTP/3 lavora sulla stessa porta UDP (QUIC) e viene annunciato ai client tramite l'header Alt-Svc.
WebSocket, SSE e gRPC sono già progettati attorno allo stesso modello di un unico listener con rilevamento del protocollo, ma sono ancora in lavorazione (vedi Roadmap).
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();Perché
L'obiettivo del server è liberare il potenziale delle applicazioni PHP concorrenti.
TrueAsync ha dato al linguaggio coroutine reali, I/O non bloccante e pool di connessioni. Perché quel potenziale si traduca in carichi di produzione, serve un server progettato fin dall'inizio attorno a questo modello: un processo a vita lunga con un event loop, in cui ogni richiesta riceve la propria coroutine e lo scheduler alterna tra di esse a ogni attesa di I/O.
TrueAsync Server è esattamente questo server. Nessuno strato intermedio fra coroutine e rete: listener, parser del protocollo, dispatcher delle richieste e handler vivono nello stesso processo e nello stesso event loop. Le connessioni al database si riusano tramite Async\Pool, opcache resta caldo tra una richiesta e l'altra, il cold start si paga una volta sola, su start().
Funzionalità
| Stato | Funzionalità | Dettagli |
|---|---|---|
| Pronto | HTTP/1.1 | Conformità completa a RFC 9112, keep-alive, pipelining (tramite llhttp, lo stesso parser usato da Node.js) |
| Pronto | HTTP/2 | Multiplexing, server push (libnghttp2 ≥ 1.57, soglia per CVE-2023-44487) |
| Pronto | HTTP/3 / QUIC | Trasporto UDP su libngtcp2 + libnghttp3, API QUIC TLS di OpenSSL 3.5 |
| Pronto | TLS 1.2 / 1.3 | OpenSSL 3.x, negoziazione ALPN, cifrari deboli disattivati |
| Pronto | Compressione | gzip (zlib-ng / zlib), Brotli, zstd: sia in uscita sia in decompressione dei corpi in ingresso, su tutti i protocolli |
| Pronto | Multipart / upload file | Parser di streaming zero-copy |
| Pronto | Contropressione | CoDel (RFC 8289), sospensione adattiva dell'accept sotto carico |
| Pronto | Streaming del corpo della richiesta | Opzionale tramite HttpRequest::readBody(); upload senza tenere il corpo in RAM |
| Pronto | sendFile | Invio efficiente di file dal disco direttamente dall'handler |
| Pronto | Pool di worker integrato | setWorkers(N): N thread tramite Async\ThreadPool + SO_REUSEPORT |
| Pronto | Scope per richiesta | Ogni handler nel proprio scope; Async\request_context() fornisce un contesto comune a tutto l'albero di coroutine della richiesta |
| Pronto | Coroutine native | Integrazione profonda con TrueAsync: qualunque I/O bloccante nell'handler sospende la coroutine, non il thread |
| Pronto | Zero-copy | Allocazioni minime sul percorso caldo |
| In roadmap | WebSocket | RFC 6455, Upgrade da HTTP/1.1 e HTTP/2 |
| In roadmap | SSE | Server-Sent Events |
| In roadmap | gRPC | Sopra HTTP/2, unary e streaming |
Architettura: event loop a thread singolo
Lo stesso modello usato da NGINX, Envoy, Node.js e da Rust Tokio/hyper.
Un solo thread possiede sia la connessione sia la richiesta, dall'accept fino al send. Nessun passaggio fra accept-thread e worker-thread, nessun lock, nessun cambio di contesto fra di loro. Un unico event loop accetta la connessione, legge i byte dal socket, fa il parsing HTTP, smista la richiesta all'handler e scrive la risposta, senza mai lasciare il thread.
┌─────────────────────────────────────────┐
│ Event Loop Thread │
│ │
accept ─► parse ─► dispatch ─► respond │
│ ▲ │ │
│ └──── coroutine yield ◄──┘ │
└─────────────────────────────────────────┘L'I/O non bloccante è gestito dal reactor libuv (tramite TrueAsync). Quando una coroutine deve attendere un file, il database o il prossimo frame WebSocket, cede il controllo all'event loop, che sceglie subito il prossimo evento pronto. Il thread non resta mai fermo in read()/recv().
Per scalare sui core si attiva la modalità multi-worker tramite setWorkers(N): l'Async\ThreadPool integrato avvia N thread OS, ciascuno con il proprio event loop indipendente, e SO_REUSEPORT (Linux/BSD) lascia che sia il kernel a distribuire le connessioni in ingresso fra di loro. Nessuno stato condiviso, nessun lock globale.
Da dove iniziare
- Avvio rapido: installazione ed esempio minimo in 5 minuti
- Configurazione: listener, worker, TLS, timeout, streaming del corpo, bootloader
- Compressione: gzip / brotli / zstd, negoziazione, BREACH
- File statici e sendFile:
StaticHandler, sidecar precompressi, Range - Streaming: streaming del corpo della richiesta e della risposta
- Multi-worker:
setWorkers(N), bootloader, scope per richiesta - Esempi: API JSON, file statici, fan-out, upload multipart
- Architettura: internals
Riferimento API
TrueAsync\HttpServerTrueAsync\HttpServerConfigTrueAsync\HttpRequestTrueAsync\HttpResponseTrueAsync\StaticHandlerTrueAsync\SendFileOptionsTrueAsync\UploadedFileTrueAsync\LogSeverity- Eccezioni
Alternative
FrankenPHP è un server embeddable separato basato su Caddy/Go, in cui PHP funge da worker. Conveniente quando servono le funzionalità di Caddy (Let's Encrypt automatico, configurazione tramite Caddyfile) o l'integrazione in un'infrastruttura Caddy esistente. TrueAsync Server è l'alternativa nativa senza runtime Go: il server vive direttamente nel processo PHP.