TrueAsync\HttpResponse
(PHP 8.6+, true_async_server 0.6+)
Oggetto risposta con interfaccia fluent. Passato come secondo parametro all'handler. È creato dal server e non viene costruito dall'utente.
namespace TrueAsync;
final class HttpResponse
{
// stato
public function setStatusCode(int $code): static;
public function getStatusCode(): int;
public function setReasonPhrase(string $phrase): static;
public function getReasonPhrase(): string;
// header
public function setHeader(string $name, string|array $value): static;
public function addHeader(string $name, string|array $value): static;
public function hasHeader(string $name): bool;
public function getHeader(string $name): ?string;
public function getHeaderLine(string $name): string;
public function getHeaders(): array;
public function resetHeaders(): static;
// trailer (HTTP/2)
public function setTrailer(string $name, string $value): static;
public function setTrailers(array $trailers): static;
public function resetTrailers(): static;
public function getTrailers(): array;
// introspezione del protocollo
public function getProtocolName(): string;
public function getProtocolVersion(): string;
// corpo
public function write(string $data): static;
public function send(string $chunk): static;
public function sendable(): bool;
public function setNoCompression(): static;
public function getBody(): string;
public function setBody(string $body): static;
public function getBodyStream(): mixed; // TODO
public function setBodyStream(mixed $stream): static; // TODO
// helper
public function json(array|string|object|null|int|float|bool $data, int $status = 200, int $flags = 0): static;
public function html(string $html): static;
public function redirect(string $url, int $status = 302): static;
// send / stato
public function end(?string $data = null): void;
public function sendFile(string $path, ?SendFileOptions $options = null): void;
public function isHeadersSent(): bool;
public function isClosed(): bool;
}Stato
setStatusCode
public HttpResponse::setStatusCode(int $code): staticCodice HTTP 100..599.
getStatusCode
public HttpResponse::getStatusCode(): intsetReasonPhrase / getReasonPhrase
public HttpResponse::setReasonPhrase(string $phrase): static
public HttpResponse::getReasonPhrase(): string"OK", "Not Found", ecc.
Header
setHeader
public HttpResponse::setHeader(string $name, string|array $value): staticImposta un header, sostituendo i valori precedenti.
addHeader
public HttpResponse::addHeader(string $name, string|array $value): staticAggiunge un valore agli esistenti (es. Set-Cookie).
hasHeader / getHeader / getHeaderLine / getHeaders
public HttpResponse::hasHeader(string $name): bool
public HttpResponse::getHeader(string $name): ?string
public HttpResponse::getHeaderLine(string $name): string
public HttpResponse::getHeaders(): arrayLettura case-insensitive di quanto impostato dall'handler.
resetHeaders
public HttpResponse::resetHeaders(): staticRimuove tutti gli header.
Trailer (HTTP/2)
Frame HEADERS inviato dopo il corpo. Consumatore canonico: gRPC (grpc-status). Su HTTP/1.1 il valore viene ignorato silenziosamente: l'emissione dei trailer nella codifica chunked non rientra in Step 5b.
setTrailer
public HttpResponse::setTrailer(string $name, string $value): staticNome in lowercase (RFC 9113 §8.2.2); il maiuscolo viene convertito automaticamente.
setTrailers
public HttpResponse::setTrailers(array $trailers): staticSet in blocco. I trailer esistenti vengono mantenuti: per uno stato pulito chiama prima resetTrailers().
resetTrailers
public HttpResponse::resetTrailers(): staticgetTrailers
public HttpResponse::getTrailers(): arrayProtocollo
getProtocolName / getProtocolVersion
public HttpResponse::getProtocolName(): string // sempre "HTTP"
public HttpResponse::getProtocolVersion(): string // "1.1", "2", "3"Corpo
write
public HttpResponse::write(string $data): staticAppend nel buffer del corpo. L'invio avviene su end() / automaticamente al ritorno dall'handler.
send
public HttpResponse::send(string $chunk): staticInvia un blocco al client (streaming).
- Il primo
send()consolida stato + header: non sono più modificabili. - I successivi aggiungono frame DATA (HTTP/2) o chunk (HTTP/1).
- Sospende la coroutine dell'handler solo in caso di contropressione (staging buffer per stream pieno). Soglia di contropressione predefinita:
setStreamWriteBufferBytes()— 256 KiB. - In condizioni normali ritorna subito.
sendable
public HttpResponse::sendable(): boolControllo informativo non bloccante:
true:send()accetterà un blocco senza sospendere la coroutine.false:send()si bloccherà per contropressione, oppure la risposta è già sigillata dasendFile()/ chiusa, oppure il tipo di risposta non è capace di streaming.
send() è sempre sicuro da chiamare: sendable() dà solo all'handler la possibilità di occuparsi di altro lavoro invece di bloccarsi su un peer lento.
setNoCompression
public HttpResponse::setNoCompression(): staticDisattiva la compressione per questa risposta: ha la precedenza su Accept-Encoding, whitelist MIME e soglia di dimensione. Da usare su endpoint sensibili a BREACH (segreti + input utente riflesso), payload con Content-Encoding già impostato, corpi che il server non deve incapsulare. Idempotente.
getBody / setBody
public HttpResponse::getBody(): string
public HttpResponse::setBody(string $body): staticGet/set del contenuto corrente del buffer.
Helper
json
public HttpResponse::json(
array|string|object|null|int|float|bool $data,
int $status = 200,
int $flags = 0
): staticSerializzazione JSON tramite php_json_encode_ex (lo stesso path di json_encode()):
array/object/ scalare$data→ codificato.string$data→ inviato così com'è (JSON in cache, byte già pronti). Niente ricodifica.
Content-Type: application/json viene impostato solo se l'handler non ne ha già impostato uno — incatena setHeader('Content-Type', 'application/problem+json')->json($payload) per un media-type diverso.
$flags: bitmask JSON_*. 0: default del server da HttpServerConfig::setJsonEncodeFlags() (JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES di fabbrica).
JSON_THROW_ON_ERROR viene rimosso silenziosamente: un errore di encode produce un 500 di errore JSON, l'eccezione non viene propagata. L'handler non deve mai avvolgere json() in try/catch.
html
public HttpResponse::html(string $html): staticImposta Content-Type: text/html.
redirect
public HttpResponse::redirect(string $url, int $status = 302): staticInvio
end
public HttpResponse::end(?string $data = null): voidTermina la risposta e la invia al client. Dopo end() non si può più scrivere nulla.
sendFile
public HttpResponse::sendFile(string $path, ?SendFileOptions $options = null): voidConsegna del file pilotata dall'handler. Registra path + options sulla risposta e ritorna subito: il trasferimento avviene in fase di dispose tramite la stessa FSM di StaticHandler (MIME, ETag, IMF-date, Range, GET condizionali, sidecar precompressi).
Dopo sendFile() la risposta è sigillata: setHeader / setStatus* / write / send / setBody / json / html / redirect / end / un altro sendFile() lanciano HttpServerRuntimeException.
Il path è fidato (l'handler ha già deciso sull'accesso). Gli errori di open/fstat (ENOENT, EACCES, oversize, non regolare) producono 500, perché gli header non sono ancora sulla rete.
Il middleware di compressione viene bypassato per i corpi sendFile (pipeline di consegna dedicata).
Il path HTTP/3 per
sendFile()è in lavorazione; per ora il dispose-hook H3 rifiuta con 500.
Vedi SendFileOptions.
Stato
isHeadersSent
public HttpResponse::isHeadersSent(): boolisClosed
public HttpResponse::isClosed(): boolEsempio
use TrueAsync\HttpResponse;
use TrueAsync\SendFileOptions;
use TrueAsync\SendFileDisposition;
$server->addHttpHandler(function ($req, HttpResponse $res) {
// SSE
if ($req->getPath() === '/events') {
$res
->setStatusCode(200)
->setHeader('Content-Type', 'text/event-stream')
->setHeader('Cache-Control', 'no-store')
->setNoCompression();
foreach (loadEvents() as $event) {
$res->send("data: " . json_encode($event) . "\n\n");
}
return;
}
// sendFile
if ($req->getPath() === '/report.pdf') {
$res->sendFile('/var/reports/q1.pdf', new SendFileOptions(
disposition: SendFileDisposition::ATTACHMENT,
downloadName: 'Q1-Report.pdf',
));
return;
}
// JSON
$res->json(['ok' => true]);
});