NAME

Hyperman::Writer - the object that writes a response body a piece at a time

SYNOPSIS

# from a psgi.streaming responder
sub {
    my $env = shift;
    return sub {
        my $respond = shift;
        my $w = $respond->([ 200, [ 'Content-Type' => 'text/plain' ] ]);
        $w->write("a piece;");
        $w->write("another");
        $w->close;
    };
}

# or from the stream seam, which also works on HTTP/2 and on TLS
sub {
    my $env = shift;
    return sub {
        my $w = Hyperman::stream($env, 200,
                    [ 'Content-Type' => 'text/plain' ]);
        $w->write($_) for @chunks;
        $w->close;
    };
}

DESCRIPTION

Two methods and one promise: what you write goes out in order, and when you close it the body has ended. You never construct one - it comes back from a psgi.streaming responder or from "stream" in Hyperman.

Underneath there are three transports, and a writer is a blessed arrayref whose shape says which:

  • an HTTP/1.1 connection. When the request was HTTP/1.1 and allowed keep-alive, the body is sent with Transfer-Encoding: chunked (or counted against the Content-Length the application gave) and the connection goes on to its next request after close. Otherwise - HTTP/1.0, a request that said Connection: close, a 101 upgrade - the response carries Connection: close and the client reads to end of file;

  • an HTTP/2 stream, buffered until close and then submitted as one response;

  • an ABI v6 stream handle, which is the transport-neutral one. The same three calls write over HTTP/1.1 or over an HTTP/2 stream, in clear or under TLS, and the branch lives inside the handle rather than in the caller. This is what "stream" in Hyperman hands back.

Three shapes rather than three classes, because they are one contract. Code holding a writer never has to ask which it has.

METHODS

write

$w->write($bytes);

Append to the body. The bytes are copied, so the caller may reuse its buffer.

Writing to a stream whose connection has gone is dropped rather than fatal: by then the reader is already lost, and a producer looping over rows has nothing useful to do with an exception. A C consumer that does want to know - to stop a query, or to release a subscription - gets the reason from the C ABI's stream_write instead, along with the backpressure signal that says when to pause. See "Stream handles" in Hyperman.

close

$w->close;

End the body. On HTTP/1.1 the connection stays open for its next request when the body was chunked or counted, and closes when it was EOF-delimited; on HTTP/2 it ends that stream and leaves the connection serving its others. A Content-Length the application gave is held to: writing past it, or closing short of it, ends the connection, because the framing is broken and nothing after it could be trusted.

Closing twice is harmless. A writer that goes out of scope without being closed is closed then, the way a lexical filehandle is - but close it yourself where you can, so the body ends when you meant it to and not when the last reference happens to go.

abort

$w->abort;

End the body so the client can tell it was not finished. Use it where the producer failed part way - the query died, the source went away - and the bytes already sent are not a whole response.

close is the wrong answer there and quietly so. A streamed body has no declared length, so there is nothing for the client to check: a body that stops early and ends cleanly arrives as a complete short one, which is the export that silently lost its last thousand rows. What the client needs is to be told, and that is a different signal on each transport - RST_STREAM with INTERNAL_ERROR on HTTP/2, killing this stream and leaving the connection's others alone, and a connection reset on HTTP/1.1, where the graceful close is itself the success signal and a reset is the only thing distinguishable from it. Output still queued is discarded on HTTP/1.1; that is the price of being able to say so at all.

Returns 0 when the stream was aborted, or a negative value when there was nothing live to abort - -2 for a stream whose connection had already gone, -1 for a writer that is not the stream-handle shape (the older psgi.streaming writers have no such verb, and are left alone rather than being closed gracefully behind your back).

Call abort or close, never both.

SEE ALSO

Hyperman, and "stream" in Hyperman for the seam that returns a transport-neutral writer.

AUTHOR

LNATION, <email at lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under the Artistic License 2.0.