Interface
ClientRequest
One HTTP exchange with a client: the request to read, and the response to write. The application's RequestHandler is called once for each, and may keep it for as long as it likes (an event stream, a long download) by simply not returning.
Introduction#
As a Duplex, read() is the request body ('' only once it ended) and write() the
response body; the module does the HTTP framing. The final response head is held until the
first write(), end(), flush() or sendFile(), and goes out with the first body bytes in one
packet; a response with no body and no content-length gets content-length: 0. No
sendResponseHeaders() before a write means an implicit 200 with no headers of yours.
The module adds date and server: Swerve (unless you send a server), chooses the framing
and owns the hop-by-hop headers: a content-length frames the body as it is, otherwise it is
chunked (HTTP/1.1) or ends with the connection (HTTP/1.0). A transfer-encoding of chunked
is the same as none; any other value throws.
A handler that throws before the head is committed makes a 500 (logged); after, the connection
is aborted. After a 101, the ClientRequest is the raw connection, and read() returns first
what the client sent behind its Upgrade request.
Class synopsis#
interface ClientRequest extends Duplex {
/* Methods */
public ClientRequest::getMethod(): string
public ClientRequest::getTarget(): string
public ClientRequest::getProtocolVersion(): string
public ClientRequest::getScheme(): string
public ClientRequest::getRequestHeaders(): array
public ClientRequest::sendResponseHeaders(int $status, array $headers = []): void
public ClientRequest::headersSent(): bool
public ClientRequest::flush(): void
public ClientRequest::sendFile($stream, ?int $offset = null, ?int $length = null): void
public ClientRequest::end(?array $trailers = null): void
}Methods#
| Method | Description |
|---|---|
getMethod | The request method, as sent: 'GET', 'POST', ... |
getTarget | The request target, as sent: '/path?query', '*' or an absolute URI. |
getProtocolVersion | '1.0' or '1.1'. |
getScheme | 'http' or 'https'; from a trusted proxy's X-Forwarded-Proto (see --trusted-proxy). |
getRequestHeaders | The request headers: lowercase name => the values, in order. host is among them. |
sendResponseHeaders | Set the response's status and headers. |
headersSent | Whether the final head is committed (it may still be held back until the first body bytes). |
flush | Commit the head (an implicit 200) and put it on the wire, with nothing buffered. |
sendFile | Write a file, or part of one, as the body: commits the head, then streams it as write() does, under the same framing rules. No content-length is computed; send one with the head. |
end | Finish the response. With no head sent, an implicit 200. |
Examples#
use Swerve\ClientRequest;
use Swerve\RequestHandler;
$app = new RequestHandler(function (ClientRequest $request) {
$name = $request->getRequestHeaders()['x-name'][0] ?? 'world';
$request->sendResponseHeaders(200, ['content-type' => 'text/plain']);
$request->write("Hello, $name: {$request->getMethod()} {$request->getTarget()}\n");
});
if (debug_backtrace()) {
return $app; // swerve loads this file: it is the application's swerve.php
}
// Run directly: serve the application with swerve, and send it a request
require __DIR__ . '/../serve.php';
serve(__FILE__, ["GET /greeting?lang=en HTTP/1.1\r\nHost: example\r\nX-Name: Ann\r\n\r\n"]);HTTP/1.1 200 OK content-type: text/plain Server: Swerve Transfer-Encoding: chunked Connection: close 22 Hello, Ann: GET /greeting?lang=en 0
return new Swerve\RequestHandler(function (Swerve\ClientRequest $request) {
$request->sendResponseHeaders(200, ['content-type' => 'text/event-stream']);
foreach ([1, 2, 3] as $n) {
$request->write("data: $n\n\n");
$request->flush();
phasync::sleep(1);
}
});See also#
RequestHandlerWhat an application's swerve.php returns: the callable that serves each HTTP exchange.