TrueAsync\HttpResponse
(PHP 8.6+, true_async_server 0.6+)
Response-Objekt mit Fluent-Interface. Wird als zweiter Parameter an den Handler übergeben. Wird vom Server erzeugt — nicht vom Benutzer konstruiert.
namespace TrueAsync;
final class HttpResponse
{
// status
public function setStatusCode(int $code): static;
public function getStatusCode(): int;
public function setReasonPhrase(string $phrase): static;
public function getReasonPhrase(): string;
// headers
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;
// trailers (HTTP/2)
public function setTrailer(string $name, string $value): static;
public function setTrailers(array $trailers): static;
public function resetTrailers(): static;
public function getTrailers(): array;
// protocol introspection
public function getProtocolName(): string;
public function getProtocolVersion(): string;
// body
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
// helpers
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 / state
public function end(?string $data = null): void;
public function sendFile(string $path, ?SendFileOptions $options = null): void;
// Server-Sent Events (text/event-stream)
public function sseStart(): static;
public function sseEvent(?string $data = null, ?string $event = null, ?string $id = null, ?int $retry = null): static;
public function sseComment(string $text = ""): static;
public function sseRetry(int $milliseconds): static;
public function isHeadersSent(): bool;
public function isClosed(): bool;
}Status
setStatusCode
public HttpResponse::setStatusCode(int $code): staticHTTP-Code 100..599.
getStatusCode
public HttpResponse::getStatusCode(): intsetReasonPhrase / getReasonPhrase
public HttpResponse::setReasonPhrase(string $phrase): static
public HttpResponse::getReasonPhrase(): string"OK", "Not Found" usw.
Header
setHeader
public HttpResponse::setHeader(string $name, string|array $value): staticHeader setzen und vorhandene Werte ersetzen.
addHeader
public HttpResponse::addHeader(string $name, string|array $value): staticWert zu vorhandenen hinzufügen (z. B. 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(): arrayCase-insensitives Lesen dessen, was der Handler gesetzt hat.
resetHeaders
public HttpResponse::resetHeaders(): staticAlle Header entfernen.
Trailers (HTTP/2)
HEADERS-Frame, der nach dem Body gesendet wird. Kanonischer Consumer ist gRPC (grpc-status). Auf HTTP/1.1 wird der Wert stillschweigend ignoriert — Trailer-Emission via Chunked Encoding ist nicht im Scope von Step 5b.
setTrailer
public HttpResponse::setTrailer(string $name, string $value): staticName — lowercase (RFC 9113 §8.2.2); Großbuchstaben werden automatisch konvertiert.
setTrailers
public HttpResponse::setTrailers(array $trailers): staticBulk-Set. Vorhandene Trailers bleiben erhalten — für einen Clean Slate vorher resetTrailers() aufrufen.
resetTrailers
public HttpResponse::resetTrailers(): staticgetTrailers
public HttpResponse::getTrailers(): arrayProtokoll
getProtocolName / getProtocolVersion
public HttpResponse::getProtocolName(): string // immer "HTTP"
public HttpResponse::getProtocolVersion(): string // "1.1", "2", "3"Body
write
public HttpResponse::write(string $data): staticAppend in den internen Body-Buffer. Versendet wird bei end() / automatisch beim Rückkehr aus dem Handler.
send
public HttpResponse::send(string $chunk): staticChunk an den Client senden (Streaming).
- Der erste
send()committet Status + Header — danach nicht mehr änderbar. - Folgende — Append von DATA-Frames (HTTP/2) oder chunked Segments (HTTP/1).
- Blockiert die Handler-Coroutine nur unter Backpressure (Per-Stream-Staging-Buffer voll). Default-Schwelle:
setStreamWriteBufferBytes()— 256 KiB. - Im Normalfall kehrt es sofort zurück.
sendable
public HttpResponse::sendable(): boolAdvisory Non-Blocking-Check:
true—send()nimmt den Chunk ohne Suspend an.false—send()blockiert auf Backpressure, oder die Response wurde vonsendFile()versiegelt / ist geschlossen, oder es ist kein streaming-fähiger Antworttyp.
send() ist immer sicher aufrufbar — sendable() gibt dem Handler nur die Möglichkeit, andere Arbeit zu erledigen, statt auf einem langsamen Peer zu blockieren.
setNoCompression
public HttpResponse::setNoCompression(): staticKomprimierung für diese Response verbieten — überschreibt Accept-Encoding, MIME-Whitelist und Size-Threshold. Anwenden bei: BREACH-sensiblen Endpoints (Secrets + reflected User-Input), Payloads mit bereits gesetztem Content-Encoding, Bodies, die der Server nicht wrappen soll. Idempotent.
getBody / setBody
public HttpResponse::getBody(): string
public HttpResponse::setBody(string $body): staticGet/Set des aktuellen Buffer-Inhalts.
Helpers
json
public HttpResponse::json(
array|string|object|null|int|float|bool $data,
int $status = 200,
int $flags = 0
): staticJSON-Serialisierung über php_json_encode_ex (derselbe Pfad wie json_encode()):
array/object/ Skalar-$data→ encoded.string$data→ wird wie es ist gesendet (cached JSON, pre-built Bytes). Spart Re-Encoding.
Content-Type: application/json wird nur gesetzt, wenn der Handler keinen eigenen vergeben hat — chainen Sie setHeader('Content-Type', 'application/problem+json')->json($payload) für einen anderen Media-Type.
$flags — JSON_*-Bitmask. 0 — Server-Defaults aus HttpServerConfig::setJsonEncodeFlags() (JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES out of the box).
JSON_THROW_ON_ERROR wird stillschweigend entfernt: ein Encoding-Fehler liefert eine 500 mit JSON-Fehlerbody, eine Exception wird nicht durchgereicht. Der Handler sollte json() nie in einen try/catch wrappen.
html
public HttpResponse::html(string $html): staticSetzt Content-Type: text/html.
redirect
public HttpResponse::redirect(string $url, int $status = 302): staticSenden
end
public HttpResponse::end(?string $data = null): voidAntwort abschließen und an den Client senden. Nach end() darf nichts mehr geschrieben werden.
sendFile
public HttpResponse::sendFile(string $path, ?SendFileOptions $options = null): voidHandler-gesteuerte Dateiauslieferung. Schreibt Pfad + Optionen auf die Response und kehrt sofort zurück — die Übertragung läuft in der Dispose-Phase über dieselbe FSM wie StaticHandler (MIME, ETag, IMF-Date, Range, Conditional GET, Precompressed Sidecars).
Nach sendFile() ist die Response sealed: setHeader / setStatus* / write / send / setBody / json / html / redirect / end / erneutes sendFile() werfen HttpServerRuntimeException.
Der Pfad ist vertrauenswürdig (Handler hat über den Zugriff entschieden). Open/fstat-Fehler (ENOENT, EACCES, oversize, non-regular) — 500, da die Header noch nicht auf dem Draht sind.
Compression Middleware wird für sendFile-Bodies umgangen (eigene Delivery-Pipeline).
Der HTTP/3-Pfad für
sendFile()ist noch in Arbeit; aktuell weist der H3-Dispose-Hook mit 500 zurück.
Siehe SendFileOptions.
Server-Sent Events (text/event-stream)
(true_async_server 0.8+). Guide mit Beispielen: SSE.
sseStart
public HttpResponse::sseStart(): staticSchaltet die Antwort in den SSE-Modus und schreibt die Header fest: Content-Type: text/event-stream, Cache-Control: no-cache, no-transform, X-Accel-Buffering: no, und markiert die Antwort als nicht komprimierbar. Die Antwort geht in den Streaming-Modus über, genau wie es das erste send() tun würde: Status und Header werden committet und können nicht mehr geändert werden, aber der Event-Payload selbst ist noch nicht auf dem Draht.
Der Aufruf ist optional: der erste sseEvent()/sseComment() startet den Stream ebenfalls von sich aus. sseStart() allein flusht Statuszeile und Header nicht: das Commit ist lazy und erfolgt beim ersten sseEvent()/sseComment()/sseRetry() (wird nie einer davon aufgerufen, wird beim Ende der Antwort ein leeres 200 text/event-stream geflusht). Um den Stream sofort zu öffnen, zum Beispiel um das onopen des Browsers freizuschalten, bevor ein echtes Event bereit ist, senden Sie ein initiales sseComment().
Wirft HttpServerInvalidArgumentException, wenn der Handler bereits einen anderen Content-Type als text/event-stream gesetzt hat, und HttpServerRuntimeException, wenn die Antwort bereits streamt, geschlossen ist oder mit sendFile() beschäftigt ist.
sseEvent
public HttpResponse::sseEvent(
?string $data = null,
?string $event = null,
?string $id = null,
?int $retry = null
): staticFormatiert und sendet ein SSE-Event, startet den Stream bei Bedarf. Mehrzeiliges $data wird an \n/\r\n/\r aufgeteilt und als mehrere data:-Felder gesendet (WHATWG §9.2). $event, $id und $retry werden nur eingefügt, wenn sie nicht null sind. Der Datensatz endet mit einer Leerzeile, damit der Browser das Event sofort verarbeitet.
$event und $id dürfen kein \r/\n enthalten (sonst würde der Parser das als Feld-/Datensatztrenner lesen), und $id darf kein NUL enthalten: Verstöße werfen HttpServerInvalidArgumentException. $retry muss nicht-negativ sein.
$data === "" ist ebenfalls ein gültiger Wert, er löst ein leeres MessageEvent aus. Alle vier Argumente auf null gesetzt ist ein No-op; der EventSource-Parser überspringt ein Event ohne data und ohne retry.
sseComment
public HttpResponse::sseComment(string $text = ""): staticSendet eine Kommentarzeile (ein Datensatz, der mit : beginnt). Browser ignorieren Kommentare, aber sie halten die Verbindung durch die Idle-Timeouts zwischengeschalteter Proxys am Leben (nginx' proxy_read_timeout, standardmäßig 60s). Der kanonische Payload ist ein leerer String (:\n\n auf dem Draht). $text darf kein \r/\n enthalten. Startet den Stream, falls er noch nicht läuft.
sseRetry
public HttpResponse::sseRetry(int $milliseconds): staticSendet eine bloße retry:-Direktive, die dem Browser mitteilt, wie viele Millisekunden er warten soll, bevor er nach einem Abbruch des Streams erneut verbindet. Zucker für sseEvent(retry: $milliseconds) ohne Payload. Startet den Stream, falls er noch nicht läuft.
State
isHeadersSent
public HttpResponse::isHeadersSent(): boolisClosed
public HttpResponse::isClosed(): boolBeispiel
use TrueAsync\HttpResponse;
use TrueAsync\SendFileOptions;
use TrueAsync\SendFileDisposition;
$server->addHttpHandler(function ($req, HttpResponse $res) {
// SSE
if ($req->getPath() === '/events') {
foreach (loadEvents() as $event) {
$res->sseEvent(json_encode($event));
}
$res->end();
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]);
});