TrueAsync\HttpResponse
(PHP 8.6+, true_async_server 0.6+)
Objeto de respuesta con interfaz fluent. Se pasa como segundo parámetro al manejador. Lo crea el servidor; no se construye desde el código de usuario.
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;
// cabeceras
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;
// introspección de protocolo
public function getProtocolName(): string;
public function getProtocolVersion(): string;
// cuerpo
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;
// envío / estado
public function end(?string $data = null): void;
public function sendFile(string $path, ?SendFileOptions $options = null): void;
public function isHeadersSent(): bool;
public function isClosed(): bool;
}Status
setStatusCode
public HttpResponse::setStatusCode(int $code): staticCódigo HTTP 100..599.
getStatusCode
public HttpResponse::getStatusCode(): intsetReasonPhrase / getReasonPhrase
public HttpResponse::setReasonPhrase(string $phrase): static
public HttpResponse::getReasonPhrase(): string"OK", "Not Found", etc.
Cabeceras
setHeader
public HttpResponse::setHeader(string $name, string|array $value): staticEstablece una cabecera reemplazando los valores anteriores.
addHeader
public HttpResponse::addHeader(string $name, string|array $value): staticAñade un valor a los existentes (por ejemplo 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(): arrayLectura case-insensitive de lo que ha puesto el handler.
resetHeaders
public HttpResponse::resetHeaders(): staticQuita todas las cabeceras.
Trailers (HTTP/2)
Frame HEADERS enviado tras el cuerpo. Consumidor canónico: gRPC (grpc-status). En HTTP/1.1 el valor se ignora silenciosamente: la emisión de trailers en codificación chunked no está en scope del Step 5b.
setTrailer
public HttpResponse::setTrailer(string $name, string $value): staticEl nombre es minúsculas (RFC 9113 §8.2.2); las mayúsculas se convierten automáticamente.
setTrailers
public HttpResponse::setTrailers(array $trailers): staticBulk-set. Los trailers existentes se conservan; para limpieza total llama antes a resetTrailers().
resetTrailers
public HttpResponse::resetTrailers(): staticgetTrailers
public HttpResponse::getTrailers(): arrayProtocolo
getProtocolName / getProtocolVersion
public HttpResponse::getProtocolName(): string // siempre "HTTP"
public HttpResponse::getProtocolVersion(): string // "1.1", "2", "3"Cuerpo
write
public HttpResponse::write(string $data): staticAppend al buffer interno del body. El envío se hace en end() / al volver automáticamente del manejador.
send
public HttpResponse::send(string $chunk): staticEnvía un chunk al cliente (streaming).
- El primer
send()fija el status + las cabeceras; ya no se pueden cambiar. - Las siguientes llamadas hacen append de frames DATA (HTTP/2) o segmentos chunked (HTTP/1).
- Bloquea la corrutina del handler solo ante contrapresión (staging buffer per-stream lleno). Umbral por defecto:
setStreamWriteBufferBytes()= 256 KiB. - En condiciones normales regresa de inmediato.
sendable
public HttpResponse::sendable(): boolComprobación no bloqueante de carácter orientativo:
true:send()aceptará el chunk sin suspender la corrutina.false:send()se bloqueará por contrapresión, la respuesta ya está sellada porsendFile()o cerrada, o no es un tipo de respuesta que admita streaming.
send() siempre se puede llamar sin riesgo; sendable() solo da al handler la oportunidad de ocuparse de otra tarea en vez de quedarse bloqueado ante un peer lento.
setNoCompression
public HttpResponse::setNoCompression(): staticProhíbe la compresión para esta respuesta, prevaleciendo sobre Accept-Encoding, whitelist MIME y umbral de tamaño. Aplícalo en: endpoints sensibles a BREACH (secretos + user input reflejado), payloads con Content-Encoding ya establecido, bodies que el servidor no debe envolver. Idempotente.
getBody / setBody
public HttpResponse::getBody(): string
public HttpResponse::setBody(string $body): staticGet/set del contenido actual del buffer.
Helpers
json
public HttpResponse::json(
array|string|object|null|int|float|bool $data,
int $status = 200,
int $flags = 0
): staticSerialización JSON mediante php_json_encode_ex (misma ruta que json_encode()):
array/object/ scalar en$data→ codificado.stringen$data→ se envía tal cual (JSON cacheado, bytes precompuestos). Se omite la re-codificación.
Content-Type: application/json se establece solo si el handler no ha puesto el suyo. Cadena setHeader('Content-Type', 'application/problem+json')->json($payload) para otro media-type.
$flags es la bitmask JSON_*. 0: defaults del servidor de HttpServerConfig::setJsonEncodeFlags() (JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES de serie).
JSON_THROW_ON_ERROR se elimina silenciosamente: un error de encode produce un 500 JSON de error y la excepción no se propaga. El handler nunca debe envolver json() en try/catch.
html
public HttpResponse::html(string $html): staticEstablece Content-Type: text/html.
redirect
public HttpResponse::redirect(string $url, int $status = 302): staticEnvío
end
public HttpResponse::end(?string $data = null): voidTermina la respuesta y la envía al cliente. Tras end() no se puede escribir más.
sendFile
public HttpResponse::sendFile(string $path, ?SendFileOptions $options = null): voidEntrega de archivo controlada por el handler. Registra path + options en la respuesta y regresa de inmediato; la entrega se hace en la fase de dispose mediante la misma FSM que StaticHandler (MIME, ETag, IMF-date, Range, GET condicional, sidecars precomprimidos).
Tras sendFile() la respuesta queda sellada: setHeader / setStatus* / write / send / setBody / json / html / redirect / end / otra llamada a sendFile() lanzan HttpServerRuntimeException.
La ruta es de confianza (el handler decidió sobre el acceso). Los errores de open/fstat (ENOENT, EACCES, oversize, no-regular) producen 500, porque las cabeceras aún no están en el cable.
El middleware de compresión se omite para los bodies de sendFile (pipeline de entrega propia).
La ruta HTTP/3 para
sendFile()está en desarrollo; por ahora el dispose-hook de H3 responde con 500.
Véase SendFileOptions.
Estado
isHeadersSent
public HttpResponse::isHeadersSent(): boolisClosed
public HttpResponse::isClosed(): boolEjemplo
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]);
});