PAGI-Server

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

    log_level and overrode it; it is now a spelling of
    log_level => 'error', and an explicit log_level wins. Anything relying
    on quiet suppressing a more verbose log_level should set log_level
    directly.
  - Tests that both quiet a server and assert on a warn-level diagnostic
    now contradict themselves, because those diagnostics answer to the
    threshold for the first time. Set log_level => 'warn' instead.
  - Startup and shutdown message text changed. Anything matching on the old
    banner, or on "PAGI development mode - ...", needs updating.

0.002012 - 2026-09-01

  Conformance
  - Implements PAGI 0.002008 (Www sub-spec 0.5). HTTP, WebSocket and SSE
    scopes now report spec_version 0.5; the lifespan sub-spec is unchanged
    at 0.3.
  - A response body is either inline http.response.body events or a single
    file/fh event, never both. Sending file or fh after inline body bytes
    now fails that send's Future. The failed send delivers no bytes and
    leaves the response sequence state untouched, so an application may
    still recover by finishing inline.

  What may break
  - An application that prepended inline bytes to a file/fh body -- a
    generated header row ahead of a delegated export, say -- will see that
    send fail. Send the whole body inline instead, or put the leading bytes
    in the file. This is the rule that keeps a compressing intermediary from
    emitting a response whose declared encoding does not describe its bytes.
  - Tests asserting spec_version eq '0.4' on an http, websocket or sse scope
    need updating to '0.5'.

0.002011 - 2026-08-29

  Bug fixes
  - disconnect_future() returns a cancellation-isolated observer per call
    (the cached master stays private): cancelling a returned future --
    directly, or as the losing component of Future->wait_any -- no longer
    silently disarms disconnect notification for every other consumer of
    the request. Latent bug under the accessor's existing promise, made
    explicit by PAGI 0.002007. Pinned by a new t/37 subtest.

0.002010 - 2026-08-28

  Bug fixes
  - h2: a receive() called after the request has been fully delivered now
    parks until the stream ends and resolves with http.disconnect --
    matching h1 -- instead of synthesizing the terminal http.request event
    again on every call (which busy-looped the spec's own
    wait-for-disconnect receive pattern and left RST_STREAM invisible to
    a receive-based app). Same one-shot discipline the SSE closure already
    had. Pinned by t/http2/40-pending-io-at-disconnect.t.

  Specification conformance (PAGI 0.002006: core spec 0.5 / Www 0.4)
  - Scopes report pagi.version '0.5' and, for http/websocket/sse,
    spec_version '0.4' -- the feature-detection gate for the new
    settlement contract, which this server already implemented. The
    lifespan scope keeps spec_version '0.3' (its sub-spec did not bump).
  - New end-to-end conformance tests pin the settlement contract over
    real sockets on every protocol: a send parked on backpressure at
    abrupt disconnect resolves (never fails, never hangs), the resumed
    coroutine observes the completed state transition, on_disconnect is
    never invoked inside the application's send call frame, a pending
    receive resolves with the protocol's disconnect event, and h2
    RST_STREAM settles per-stream I/O without cancelling the application
    Future (t/61-pending-io-at-disconnect.t,
    t/http2/40-pending-io-at-disconnect.t).

  Maintenance
  - t/http2/36 builds its 20KB payload once, outside its send loop: a
    repeat-op inside a foreach that also awaits yields undef on later
    iterations under Future::AsyncAwait on ITHREADS perls (upstream bug,
    minimal reproducer reported; threads is the trigger -- DEBUGGING is
    not required -- so vendor threaded perls are affected too).

0.002009 - 2026-08-26

  Bug Fixes
  - Constructing a second PAGI::Server in the same process no longer
    closes the first server's listeners: PAGI_REUSE entries published by
    live servers in this process are now exempt from inherited-fd
    collection. Exec-based hot restart and forked workers are unaffected.
    (Surfaced as "Epoll cannot cope with fd changing handle under it" on
    CPAN smokers; on other loops the first server silently stopped
    accepting.)
  - An HTTP/2 response 'te' header passing the trailers carve-out is now
    submitted as the canonical lowercase token: libnghttp2 versions punish
    RFC-forbidden whitespace in field values differently, some by
    corrupting the whole response.

  Maintenance (CPAN smoker-proofing)
  - t/http2/34 accepts either RFC-legal teardown frame (GOAWAY or
    RST_STREAM) for a malformed trailer block instead of pinning one
    libnghttp2 version's choice.
  - The SSE end-to-end test requires PAGI-Tools 0.002001+; a pre-split
    PAGI install shipped a PAGI::SSE whose close() is not awaitable.
  - All cross-distribution integration tests now gate on
    INTEGRATION_TEST=1, so smoker results never depend on whichever
    PAGI-Tools version is installed.

0.002008 - 2026-08-25

  Maintenance
  - t/lifespan-mode.t no longer asserts the exact exit code of the CLI's
    --lifespan off rejection. An uncaught die's exit status is $! when
    errno is nonzero (else 255), and ambient errno at die time varies by
    platform and module-search history -- CPAN smokers' long PERL5LIB
    searches leave ENOENT set, so every Linux smoker saw exit 2 and
    failed 0.002007's exact-255 assertion. The test now requires only a
    nonzero exit; the rejection message assertions are unchanged.

  Specification Alignment
  - HTTP/1.1 responses carrying an application-supplied Upgrade header
    (e.g. 426 Upgrade Required) now also carry 'upgrade' among the
    server-supplied Connection tokens, per the PAGI spec's new Upgrade
    companion rule (RFC 9110 requires the pair from any Upgrade sender).
    The application's own Connection header is still stripped; the server
    completes the pair itself. Responses without Upgrade are unchanged
    (no Connection header; keep-alive stays implicit).

0.002007 - 2026-08-24

Changes  view on Meta::CPAN

    "HTTP/2 support requested but Net::HTTP2::nghttp2 is not installed, or
    is older than 0.009" error and install instructions, instead of
    starting. A deployment on
    0.008 requesting HTTP/2 must upgrade Net::HTTP2::nghttp2 (or drop
    http2 => 1) before the server will start at all. HTTP/1.1-only
    deployments are unaffected.
  - disconnect_future called for the first time after the request has
    already completed cleanly now returns a Future that stays pending
    forever, instead of one already resolved with undef -- a clean
    completion is not a disconnect, so there is nothing for the Future to
    resolve with. A Future requested after an abnormal disconnect is
    unaffected: still already resolved with the disconnect reason. An
    application awaiting this Future only after completion (e.g. via
    Future->wait_any alongside other work that has already finished) now
    hangs instead of resuming with undef; use on_complete to observe a
    clean completion instead.
  - SSE connection detection (HTTP/1.1 and HTTP/2) tightens from a raw
    substring scan of the Accept header to PAGI's media-range
    client-signal check: the sse scope is assigned only when the combined
    Accept header values contain the exact range text/event-stream,
    case-insensitively, with an effective quality value greater than zero;
    q=0 and wildcard ranges (*/*, text/*) never signal SSE (nor did they
    under the old substring scan). The old scan misclassified an explicit
    refusal (Accept: text/event-stream;q=0) as SSE and false-positived on
    any Accept token merely containing the substring "text/event-stream";
    a request that reached sse only via one of those quirks now correctly
    receives an http scope instead. Real SSE clients (EventSource,
    fetch-event-source) send the exact media type and are unaffected.

  Bug Fixes
  - HTTP/1.1's completed SSE decline (sse.http.response.start +
    sse.http.response.body) no longer delivers a synthesized
    sse.disconnect to an application that calls receive() again before
    returning. The decline's own teardown closed the connection via the
    same _handle_disconnect_and_close('client_closed') every abnormal end
    uses, which unconditionally synthesized sse.disconnect for any
    sse_mode connection; per the spec a decline delivers no events at all.
    A receive() call made after a completed decline now parks instead of
    resolving with a fabricated disconnect. HTTP/2's decline path already
    behaved correctly.
  - Socket read/write errors on a connection now report the spec's
    read_error/write_error disconnect reasons instead of client_closed.
    IO::Async::Stream's default behavior when no on_read_error/on_write_error
    handler is supplied is to call close (which this server's on_closed
    handler reports as client_closed), making every socket error
    indistinguishable from an ordinary clean peer disconnect. write_timeout
    remains unproduced: this server has no mechanism that times a write's
    completion independently of its other timeouts.
  - HTTP/1.1's on_drain no longer fires when a connection tears down with
    the outbound buffer still above the high mark (client disconnect,
    timeout, server shutdown, ...). arm_drain previously piggybacked
    directly on the same Future queue a blocking $send await uses, so
    teardown resolved both indiscriminately; a new, separate _drain_fires
    list now holds the on_drain callback, fired only on a genuine drain and
    dropped unfired on teardown, matching how HTTP/2 already kept
    stream_drain_waiters and transport_drain_fires apart. A producer parked
    on the blocking backpressure path still resumes on teardown either way.
  - _handle_disconnect now marks the HTTP connection-state object (and, on
    HTTP/2, every open stream's connection-state) as disconnected before
    cancelling pending drain waiters, not after. Cancelling a drain waiter
    can synchronously resume an awaiting app coroutine (a $send blocked on
    backpressure), and that resumed code's first act may be to check
    is_connected()/disconnect_reason() -- previously it could observe a
    stale "still connected" snapshot for the instant between its own
    resumption and the state being marked.
  - Fail the Future returned by $send when a file response's handle cannot be
    read, instead of treating a read error as EOF. Applications may now safely
    close a file resource only after the corresponding send Future resolves.
  - The HTTP/1 SSE decline response now sends Connection: close explicitly, so
    pooled clients do not attempt to reuse a socket the server has already
    closed.
  - http.response.trailers on an HTTP/1 response declared with Content-Length
    (not chunked) now fails the send Future ("requires chunked framing")
    instead of silently vanishing; trailers ride chunked framing only.
  - HTTP/2 WebSocket streams now deliver exactly one websocket.disconnect
    event per scope; every delivery site (peer Close frame, bare
    END_STREAM, RST_STREAM/other stream close, server-initiated protocol
    close, keepalive pong timeout) routes through a single dedup point, so
    a stream that hits more than one of these in sequence still only
    reports the first. A bare END_STREAM with no close handshake
    previously reported two events (1005/empty-reason, then a second
    1006/empty-reason from the stream close) and now reports exactly one,
    code 1006 and reason client_closed. RST_STREAM and other abnormal
    stream closes now likewise report reason client_closed instead of an
    empty string. Server-initiated protocol closes (invalid close frame,
    invalid close code, invalid UTF-8 in a text frame or close reason) now
    report the spec's protocol_error reason token instead of an ad hoc
    human-readable string (e.g. "Invalid close frame").
  - HTTP/2's "client already disconnected, nothing new to report" log
    carve-out no longer suppresses the incomplete-response/threw warning
    for a stream whose connection state recorded server_error (the SERVER
    caused the abnormal end, e.g. an early END_STREAM on a
    trailers-declared response that never sent its trailers). Per the PAGI
    spec, the carve-out applies only when the client had already
    disconnected; a server-caused end must still warn.
  - sse.send string fields, sse.comment text, and generated keepalive
    comments are now encoded to UTF-8 exactly once at the wire boundary
    (chunk framing on HTTP/1.1, the per-stream send queue on HTTP/2). For an
    application-initiated send (sse.send or sse.comment), a string that
    fails to encode now fails that send's Future instead of writing
    corrupted bytes to the socket.
  - SSE request bodies (sse.request) are now delivered completely and with
    truthful more flags on both transports. HTTP/2: the first receive
    previously returned sse.request as soon as it was called, gated only on
    a sent flag, so a POST body still arriving across DATA frames could be
    delivered truncated behind a false more => 0; it now waits for the
    stream's body to actually complete. HTTP/1.1: chunked Transfer-Encoding
    and Expect: 100-continue were never handled for SSE requests (body
    presence was inferred from Content-Length alone), so a chunked body was
    silently dropped and a client waiting for the 100-continue interim
    response would stall; both now work, sharing the same body reader as
    plain HTTP requests.
  - HTTP/2 SSE keepalive and idle-timeout state (writer, timer, interval,
    comment) is now per-stream instead of connection-level: a second
    multiplexed SSE stream's sse.start no longer hijacks the first stream's
    keepalive writer, and an idle timeout now ends only the stream that
    went idle instead of the whole connection. HTTP/1.1 is unaffected --
    one connection carries only one long-lived SSE stream, so its
    keepalive/idle state stays connection-level.
  - On sse.start, the server-supplied Cache-Control header (both
    transports) and Date header (HTTP/1.1) are now added only when the



( run in 2.455 seconds using v1.01-cache-2.11-cpan-364913b4093 )