Menu

Reference / Http

ChunkedWriter

finalclass
final class ChunkedWriter

The writer a streaming response body receives; see Response::stream().

For an HTTP/1.1 client each write() becomes one chunk of Transfer-Encoding: chunked. For an HTTP/1.0 client, which has no chunked encoding, bytes go out raw and the close of the connection ends the body. The server writes the terminating chunk itself when the closure returns.

Example

// Streaming a response body in chunks with Response::stream().

use Http\{Request, Response, Server};
use function Async\{async, spawn};

// Bind a loopback listener on a free port (no fixed port in the example).
$listener = stream_socket_server('tcp://127.0.0.1:0');
stream_set_blocking($listener, false);
$name = stream_socket_get_name($listener, false);
$port = (int) substr($name, strrpos($name, ':') + 1);

/** One request on a fresh connection; returns the raw response. */
function fetch(int $port, string $request, string $body = ''): string
{
    $c = fsockopen('127.0.0.1', $port);
    fwrite($c, $request . "Connection: close\r\n\r\n" . $body);
    $raw = '';
    while (($chunk = fread($c, 8192)) !== '' && $chunk !== false) {
        $raw .= $chunk;
    }
    fclose($c);
    return $raw;
}

$server = Server::onListener($listener)->acceptWait(0.02);

async(function () use ($server, $port) {
    spawn(fn () => $server->serve(function (Request $req): Response {
        return (new Response())->type('text/plain')->stream(function (Http\ChunkedWriter $w) {
            foreach (['alpha', 'beta', 'gamma'] as $row) {
                $w->write($row . "\n");
                $w->flush(); // without flush() the writer batches up to 64 KiB
            }
        });
    }));

    $raw = fetch($port, "GET /rows HTTP/1.1\r\nHost: localhost\r\n");
    [$head, $body] = explode("\r\n\r\n", $raw, 2);
    foreach (explode("\r\n", $head) as $line) {
        if (!str_starts_with($line, 'Date:') && !str_starts_with($line, 'Server:')) {
            echo $line, "\n";
        }
    }
    echo "-- wire body\n", str_replace("\r\n", '\r\n' . "\n", $body);
    echo "-- HTTP/1.0 client: no chunking, the close ends the body\n";
    $raw = fetch($port, "GET /rows HTTP/1.0\r\n");
    [$head, $body] = explode("\r\n\r\n", $raw, 2);
    echo strtok($head, "\r\n"), "\n", $body;
    $server->stop();
});

Output

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: close
-- wire body
6\r\n
alpha
\r\n
5\r\n
beta
\r\n
6\r\n
gamma
\r\n
0\r\n
\r\n
-- HTTP/1.0 client: no chunking, the close ends the body
HTTP/1.0 200 OK
alpha
beta
gamma

See also: Response

Methods

write()Queues one chunk.
flush()Sends everything queued to the client now.
end()Writes the terminating zero-length chunk and flushes.
bytesWritten()The number of bytes handed to the socket so far, chunk framing included.
isChunked()Whether chunked framing is in use (false for an HTTP/1.0 client).

write()#source

function write(string $s): void

Queues one chunk. An empty string is ignored, because a zero-length chunk would end the body.

Parameters

$s string
Bytes to send.

flush()#source

function flush(): void

Sends everything queued to the client now. Without it the writer batches output (up to 64 KiB) before writing to the socket.

end()#source

function end(): void

Writes the terminating zero-length chunk and flushes. Idempotent. The server calls it for you after the closure returns, so a handler normally never needs to.

bytesWritten()#source

function bytesWritten(): int

The number of bytes handed to the socket so far, chunk framing included. Bytes still queued are not counted.

Returns

int โ€” The byte count.

isChunked()#source

function isChunked(): bool

Whether chunked framing is in use (false for an HTTP/1.0 client).

Returns

bool โ€” true for chunked framing.

View source