phasync
Request handlers / ClientRequest

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#

MethodDescription
getMethodThe request method, as sent: 'GET', 'POST', ...
getTargetThe 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).
getRequestHeadersThe request headers: lowercase name => the values, in order. host is among them.
sendResponseHeadersSet the response's status and headers.
headersSentWhether the final head is committed (it may still be held back until the first body bytes).
flushCommit the head (an implicit 200) and put it on the wire, with nothing buffered.
sendFileWrite 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.
endFinish the response. With no head sent, an implicit 200.

Examples#

Example 1Read the request, write the response
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"]);
Output
HTTP/1.1 200 OK
content-type: text/plain
Server: Swerve
Transfer-Encoding: chunked
Connection: close

22
Hello, Ann: GET /greeting?lang=en

0
Example 2
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.