PAGI
view release on metacpan or search on metacpan
docs/specs/www.mkdn view on Meta::CPAN
Keys:
- `type` -- `"http.request"`
- `body` (Bytes, default `""`) -- Request body chunk
- `more` (Int, default `0`) -- `1` if more body data is forthcoming, otherwise `0`
### Response Start - `send` event
Note: Protocol servers are NOT required to flush on `http.response.start`, giving flexibility to emit an error response in case of internal application errors before data is sent.
Transfer-Encoding headers sent by the application must be ignored. Content-Encoding (e.g. gzip) is under application control.
Keys:
- `type` -- `"http.response.start"`
- `status` (Int) -- HTTP status code
- `headers` (ArrayRef[ArrayRef[Bytes]], default `[]`) -- Response headers
- `trailers` (Int, default `0`) -- `1` if trailers will be sent after body via `http.response.trailers`, otherwise `0`
### Response Body - `send` event
Keys:
- `type` -- `"http.response.body"`
- `body` (Bytes, default `""`) -- Response body chunk
- `file` (String) -- Absolute path to file for server to open and stream
- `fh` (Filehandle) -- Already-open filehandle for server to stream
- `offset` (Int, default `0`) -- Byte offset to start reading from (for range requests)
- `length` (Int, optional) -- Number of bytes to send (omit to read until EOF)
- `more` (Int, default `0`) -- Indicates more body content to follow (`1` if true, otherwise `0`). **Ignored for `file` and `fh` responses** which are implicitly complete.
The `body`, `file`, and `fh` keys are **mutually exclusive** - exactly one MUST be provided per event. Applications **MUST** provide `body` as encoded bytes. For text content, this typically means UTF-8 encoding before sending. The `Content-Length` h...
**Note:** When using `file` or `fh`, the response is implicitly complete after the file/handle contents are sent. The `more` key is ignored for these response types - there is no need to specify `more => 0`.
When `file` or `fh` is provided, servers MUST stream the file contents efficiently:
- Servers SHOULD stream large files in chunks to avoid memory bloat
- Servers MAY use zero-copy mechanisms (sendfile, splice) when appropriate
- The `offset` and `length` keys enable range request support (e.g., HTTP 206 Partial Content)
- For production file serving, consider using XSendfile middleware to delegate to a reverse proxy
- When using `file`, the server opens the file, streams it, and closes it
- When using `fh`, the application retains ownership and **MUST** close the handle after the `$send->()` Future completes
**Error Handling:**
- If `file` cannot be opened (not found, permission denied), the `$send->()` Future MUST fail with an appropriate exception
- If `fh` is invalid or closed, the `$send->()` Future MUST fail immediately
- Applications SHOULD validate file existence before sending `http.response.start` to avoid incomplete responses
**Validation:**
- `offset` MUST be a non-negative integer
- `length` MUST be a non-negative integer if provided
- If `offset` exceeds file size, servers SHOULD send zero bytes
**Examples:**
```perl
# Full file streaming
await $send->({
type => 'http.response.body',
file => '/var/www/static/large-video.mp4',
});
# Range request (bytes 1000-1999)
await $send->({
type => 'http.response.body',
file => '/var/www/static/document.pdf',
offset => 1000,
length => 1000,
});
# Streaming from already-open filehandle
open my $fh, '<:raw', '/tmp/generated-report.csv' or die $!;
await $send->({
type => 'http.response.body',
fh => $fh,
});
close $fh; # Application MUST close after send Future completes
```
### Response Trailers - `send` event
Only valid when `http.response.start` was sent with `trailers => 1`. After trailers are transmitted the server MUST consider the response body complete.
Keys:
- `type` -- `"http.response.trailers"`
- `headers` (ArrayRef[ArrayRef[Bytes]], default `[]`) -- Trailer headers encoded the same way as response headers (lower-case names, byte values)
### Disconnected Client - `send` exception
If the client disconnects or cancels the connection, servers MUST send an explicit `disconnect` event to the application.
Any subsequent `$send` invocation **must** fail its returned `Future` (or throw) with a Perl exception class that indicates the disconnect (e.g., `PAGI::Error::Disconnected`). Servers MUST NOT expose Python exceptions such as `OSError`.
Applications MUST gracefully handle disconnect events by:
- Immediately halting unnecessary processing upon disconnect
- Optionally sending minimal final acknowledgment messages
- Executing asynchronous cleanup of resources as necessary.
### Disconnect - `receive` event
Sent to the application if receive is called after a response has been sent or after the HTTP connection has been closed.
Keys:
- `type` -- `"http.disconnect"`
### Connection State
The `pagi.connection` scope key provides a mechanism for applications to detect client disconnection **without consuming messages from the receive queue**. This addresses a fundamental limitation where checking for disconnect via `receive()` may inad...
#### The Problem
In PAGI's pull-based receive model, the only way to know if a client has disconnected is to consume the next message:
```perl
my $message = await $receive->();
docs/specs/www.mkdn view on Meta::CPAN
- Setting `interval => 0` stops the keepalive timer
- Comments do not trigger client's `onmessage` handler
**Example:**
```perl
# Enable keepalive with 30s interval
await $send->({
type => 'sse.keepalive',
interval => 30,
comment => 'ping',
});
```
### SSE Send Timeout
The `sse.send` event supports an optional `timeout` field:
- `timeout` (Number, optional) -- Send timeout in seconds. If the write does not complete within this time, the Future fails and the connection is closed.
**Example:**
```perl
await $send->({
type => 'sse.send',
data => 'hello',
timeout => 5,
});
```
### SSE Disconnect - `receive` event
Sent to the application if the client disconnects or if the server shuts down the SSE stream after `sse.start`.
- `type` -- `"sse.disconnect"`
- `reason` (String, default empty) -- Human-readable disconnect reason
**Common reasons:**
| Reason | Meaning |
|--------|---------|
| `'client disconnect'` | Client closed connection normally |
| `'write error'` | Failed to write (keepalive or send) |
| `'send timeout'` | Send timeout exceeded |
## PAGI to PSGI Compatibility
PAGI translates keys explicitly to maintain compatibility with PSGI:
- `REQUEST_METHOD` -> `method`
- `SCRIPT_NAME` -> `root_path`
- `PATH_INFO` -> `path` minus `root_path`
- `QUERY_STRING` -> `query_string`
- `CONTENT_TYPE` -> extracted from `headers`
- `CONTENT_LENGTH` -> extracted from `headers`
- `SERVER_NAME`, `SERVER_PORT` -> `server`
- `REMOTE_ADDR`, `REMOTE_PORT` -> `client`
- `SERVER_PROTOCOL` -> `http_version`
- `psgi.url_scheme` -> `scheme`
- `psgi.version` -> `[1, 1]` (PAGI servers MUST advertise the PSGI version they emulate when bridging)
- `psgi.input` -> constructed from `http.request` events
- `psgi.errors` -> handled by the server as appropriate
- `psgi.streaming`, `psgi.nonblocking`, `psgi.multithread`, `psgi.multiprocess` -> derived from PAGI server capabilities and advertised via PSGI adapter docs
Response mappings:
- `status` and `headers` map directly to `http.response.start`
- Body content from PSGI maps directly to `http.response.body` messages.
## PAGI Encoding Differences
- `path`: Decoded UTF-8 string from percent-encoded input. The server first
percent-decodes `raw_path`, then attempts UTF-8 decoding of the resulting
bytes into Unicode characters. If the bytes are not valid UTF-8, the server
**should** fall back to the original percent-decoded bytes rather than
replacing invalid sequences or rejecting the request (Mojolicious-style
fallback). Applications needing strict UTF-8 validation can check `raw_path`
and decode themselves with `Encode::FB_CROAK`.
- `headers`: Represented as bytes exactly as sent/received
- `query_string`: Raw bytes from URL after `?`, percent-encoded
- `root_path`: Unicode path string matching `SCRIPT_NAME`
## Version History
- `0.2` (Draft): SSE POST method support, keepalive events, disconnect reasons,
clarified scope fields (method required for HTTP/SSE, not for WebSocket)
- `0.1` (Draft): Initial draft based on ASGI 2.5, supporting HTTP, WebSocket, and SSE.
## Copyright
This document has been placed in the public domain.
( run in 0.734 second using v1.01-cache-2.11-cpan-140bd7fdf52 )