Esempi di TrueAsync Server
(PHP 8.6+, true_async_server 0.6+)
API JSON con fan-out parallelo
php
use TrueAsync\HttpServer;
use TrueAsync\HttpServerConfig;
use function Async\spawn;
use function Async\await_all;
$server = new HttpServer(
(new HttpServerConfig())
->addListener('0.0.0.0', 8080)
->setWorkers(\Async\available_parallelism())
);
$server->addHttpHandler(function ($req, $res) {
if ($req->getPath() !== '/dashboard') {
$res->setStatusCode(404)->json(['error' => 'not found']);
return;
}
$userId = (int) $req->getQueryParam('user_id');
// Tre query indipendenti al DB, in parallelo
[$user, $posts, $followers] = await_all([
spawn(fn() => fetchUser($userId)),
spawn(fn() => fetchPosts($userId)),
spawn(fn() => fetchFollowers($userId)),
]);
$res->json([
'user' => $user,
'posts' => $posts,
'followers' => $followers,
]);
});
$server->start();File statici + dinamici
php
use TrueAsync\HttpServer;
use TrueAsync\HttpServerConfig;
use TrueAsync\StaticHandler;
use TrueAsync\StaticOnMissing;
$config = (new HttpServerConfig())
->addListener('0.0.0.0', 8080)
->setCompressionEnabled(true);
$server = new HttpServer($config);
// /assets/* viene servito da public/, nessun handler PHP
$server->addStaticHandler(
(new StaticHandler('/assets/', __DIR__ . '/public'))
->setIndexFiles('index.html')
->enablePrecompressed('br', 'gzip')
->setCacheControl('public, max-age=31536000, immutable')
);
// Tutto il resto passa per PHP
$server->addHttpHandler(function ($req, $res) {
$res->setStatusCode(200)->html('<h1>Dynamic route: ' . htmlspecialchars($req->getPath()) . '</h1>');
});
$server->start();Upload multipart con spostamento del file
php
use TrueAsync\HttpServer;
use TrueAsync\HttpServerConfig;
$server = new HttpServer(
(new HttpServerConfig())
->addListener('0.0.0.0', 8080)
->setMaxBodySize(100 * 1024 * 1024)
);
$server->addHttpHandler(function ($req, $res) {
if ($req->getMethod() !== 'POST') {
$res->setStatusCode(405); return;
}
$avatar = $req->getFile('avatar');
if ($avatar === null || !$avatar->isValid()) {
$res->setStatusCode(400)->json(['error' => 'no valid avatar']);
return;
}
if ($avatar->getSize() > 5 * 1024 * 1024) {
$res->setStatusCode(413)->json(['error' => 'too big']);
return;
}
$target = '/var/storage/avatars/' . bin2hex(random_bytes(8)) . '.bin';
$avatar->moveTo($target);
$res->json([
'saved' => $target,
'name' => $avatar->getClientFilename(),
'mime' => $avatar->getClientMediaType(),
'size' => $avatar->getSize(),
]);
});
$server->start();Streaming di un upload grande senza tenerlo in RAM
php
$server = new HttpServer(
(new HttpServerConfig())
->addListener('0.0.0.0', 8080)
->setBodyStreamingEnabled(true)
->setMaxBodySize(10 * 1024 * 1024 * 1024) // 10 GiB
);
$server->addHttpHandler(function ($req, $res) {
$id = bin2hex(random_bytes(8));
$fp = fopen("/var/storage/uploads/$id.bin", 'wb');
if ($fp === false) {
$res->setStatusCode(500)->json(['error' => 'open failed']); return;
}
$total = 0;
try {
while (($chunk = $req->readBody()) !== null) {
fwrite($fp, $chunk);
$total += strlen($chunk);
}
} finally {
fclose($fp);
}
$res->json(['id' => $id, 'size' => $total]);
});
$server->start();SSE (Server-Sent Events)
php
$server->addHttpHandler(function ($req, $res) {
if ($req->getPath() !== '/events') {
$res->setStatusCode(404); return;
}
$res
->setStatusCode(200)
->setHeader('Content-Type', 'text/event-stream')
->setHeader('Cache-Control', 'no-store')
->setHeader('X-Accel-Buffering', 'no') // amichevole per nginx
->setNoCompression(); // SSE: gli eventi devono raggiungere il client subito
for ($i = 0; $i < 60; $i++) {
$payload = json_encode(['t' => time(), 'i' => $i]);
$res->send("data: $payload\n\n");
\Async\delay(1000);
}
});Download di un file con autorizzazione
php
use TrueAsync\SendFileOptions;
use TrueAsync\SendFileDisposition;
$server->addHttpHandler(function ($req, $res) {
$token = $req->getHeader('Authorization');
if (!isValidToken($token)) {
$res->setStatusCode(401); return;
}
$reportId = preg_replace('#[^a-z0-9-]#', '', $req->getQueryParam('id') ?? '');
if ($reportId === '') {
$res->setStatusCode(400); return;
}
$res->sendFile("/var/storage/reports/$reportId.pdf", new SendFileOptions(
contentType: 'application/pdf',
disposition: SendFileDisposition::ATTACHMENT,
downloadName: "report-$reportId.pdf",
cacheControl: 'private, no-store',
));
});Redirect
php
$server->addHttpHandler(function ($req, $res) {
if ($req->getPath() === '/old-url') {
$res->redirect('/new-url', 301);
return;
}
// ...
});JSON pre-codificato (skip della ricodifica)
php
$cache = new RedisCache();
$server->addHttpHandler(function ($req, $res) use ($cache) {
$key = 'feed:' . $req->getQueryParam('uid');
$cached = $cache->get($key);
if ($cached !== null) {
// stringa → inviata così com'è, senza ricodifica
$res->json($cached);
return;
}
$payload = buildFeed($req);
$cache->set($key, $payload = json_encode($payload), 60);
$res->json($payload);
});Firewall di bailout: un fatal non abbatte il server
Handler volutamente rotto:
php
$server->addHttpHandler(function ($req, $res) {
if ($req->getPath() === '/boom') {
throw new \Error('uncaught fatal');
}
$res->setStatusCode(200)->setBody('ok');
});Una richiesta a /boom produce 500 Internal Server Error, la coroutine dell'handler viene drenata, il listener continua ad accettare nuove connessioni. Lo stesso comportamento per E_ERROR, OOM ed eccezioni non gestite durante lo shutdown. Funziona su H1, H2 e H3.
Eccezione HTTP personalizzata
TrueAsync\HttpException extends Async\AsyncCancellation. Lanciala da qualsiasi punto dell'handler per inviare uno specifico stato HTTP attraverso la normale catena di cancellazione.
php
use TrueAsync\HttpException;
class NotFoundException extends HttpException {}
class ForbiddenException extends HttpException {}
$server->addHttpHandler(function ($req, $res) {
$user = User::find($req->getQueryParam('id'))
?? throw new NotFoundException('user not found', 404);
if (!$user->canBeViewedBy(currentUser()))
throw new ForbiddenException('access denied', 403);
$res->json($user->toArray());
});Lo stato viene preso da $code (deve essere 4xx/5xx, altrimenti 500), il corpo da $message.
Vedi anche
examples/nel repository (minimal-server.php,demo-server.php,multi-worker.php,multi-worker-manual.php)- Configurazione
- Multi-worker