NAME
PAGI::Request - Convenience wrapper for PAGI request scope
SYNOPSIS
use PAGI::Request;
use Future::AsyncAwait;
async sub app {
my ($scope, $receive, $send) = @_;
my $req = PAGI::Request->new($scope, $receive);
# Basic properties
my $method = $req->method; # GET, POST, etc.
my $path = $req->path; # /users/42
my $host = $req->host; # example.com
# Query parameters (Hash::MultiValue)
my $page = $req->query('page');
my @tags = $req->query_params->get_all('tags');
# Headers
my $ct = $req->content_type;
my $auth = $req->header('authorization');
# Cookies
my $session = $req->cookie('session');
# Body parsing (async)
my $json = await $req->json; # Parse JSON body
my $form = await $req->form; # Parse form data
# File uploads (async)
my $avatar = await $req->upload('avatar');
if ($avatar && !$avatar->is_empty) {
await $avatar->save_to('/uploads/avatar.jpg');
}
# Streaming large bodies
my $stream = $req->body_stream(max_bytes => 100 * 1024 * 1024);
await $stream->stream_to_file('/uploads/large.bin');
# Auth helpers
my $token = $req->bearer_token;
my ($user, $pass) = $req->basic_auth;
# Per-request storage
$req->stash->{user} = $current_user;
}
DESCRIPTION
PAGI::Request provides a friendly interface to PAGI request data. It wraps the raw $scope hashref and $receive callback with convenient methods for accessing headers, query parameters, cookies, request body, and file uploads.
This is an optional convenience layer. Raw PAGI applications continue to work with $scope and $receive directly.
CLASS METHODS
configure
PAGI::Request->configure(
max_body_size => 10 * 1024 * 1024, # 10MB total body
max_field_size => 1 * 1024 * 1024, # 1MB per form field
max_file_size => 10 * 1024 * 1024, # 10MB per file upload
spool_threshold => 64 * 1024, # 64KB
path_param_strict => 0, # Die if path_params not in scope
);
Set class-level defaults for body/upload handling and path parameters.
- max_body_size
-
Maximum total request body size. Enforced by the server.
- max_field_size
-
Maximum size for non-file form fields in multipart requests. Default: 1MB. Protects against oversized text submissions.
- max_file_size
-
Maximum size for file uploads in multipart requests. Default: 10MB. Applies to parts with a filename in Content-Disposition.
- spool_threshold
-
Size at which multipart data is spooled to disk. Default: 64KB.
- path_param_strict
-
When set to 1,
path_paramsandpath_paramwill die if$scope->{path_params}is not defined (i.e., no router has set it). Default: 0 (return empty hashref/undef silently).This is useful for catching configuration errors where you expect a router but one isn't configured. See "Strict Mode" for details.
config
my $config = PAGI::Request->config;
Returns the current configuration hashref.
CONSTRUCTOR
new
my $req = PAGI::Request->new($scope, $receive);
Creates a new request object. $scope is required. $receive is optional but required for body/upload methods.
PROPERTIES
method
HTTP method (GET, POST, PUT, etc.)
path
Request path, UTF-8 decoded.
raw_path
Request path as raw bytes (percent-encoded).
query_string
Raw query string (without leading ?).
scheme
http or https.
host
Host from the Host header.
http_version
HTTP version (1.0 or 1.1).
client
Arrayref of [host, port] or undef.
content_type
Content-Type header value (without parameters).
content_length
Content-Length header value.
raw
Returns the raw scope hashref.
HEADER METHODS
header
my $value = $req->header('Content-Type');
Get a single header value (case-insensitive). Returns the last value if the header appears multiple times.
header_all
my @values = $req->header_all('Accept');
Get all values for a header.
headers
my $headers = $req->headers; # Hash::MultiValue
Get all headers as a Hash::MultiValue object.
QUERY PARAMETERS
query_params
my $params = $req->query_params; # Hash::MultiValue
Get query parameters as Hash::MultiValue.
query
my $value = $req->query('page');
Shortcut for $req->query_params->get($name).
PATH PARAMETERS
Path parameters are captured from the URL path by a router (e.g., PAGI::App::Router) and stored in $scope->{path_params}. This is a router-agnostic interface - any router can populate this field.
path_params
my $params = $req->path_params; # hashref
Get all path parameters as a hashref. Returns an empty hashref if no router has set path parameters.
# Route: /users/:id/posts/:post_id
# URL: /users/42/posts/100
my $params = $req->path_params;
# { id => '42', post_id => '100' }
path_param
my $id = $req->path_param('id');
Get a single path parameter by name. Returns undef if not found.
# Route: /users/:id
# URL: /users/42
my $id = $req->path_param('id'); # '42'
Strict Mode
By default, path_params and path_param return empty values if no router has set $scope->{path_params}. This is the safest behavior for middleware and handlers that may run with or without a router.
If you want to catch configuration errors early, enable strict mode:
PAGI::Request->configure(path_param_strict => 1);
With strict mode enabled, calling path_params or path_param when $scope->{path_params} is undefined will die with an error message. This helps catch bugs where you expect a router but one isn't configured.
# Strict mode: dies if no router set path_params
PAGI::Request->configure(path_param_strict => 1);
my $id = $req->path_param('id');
# Dies: "path_params not set in scope (no router configured?)"
The default is path_param_strict => 0 (non-strict), which matches Starlette's behavior of returning an empty dict when path_params is not set.
COOKIES
cookies
my $cookies = $req->cookies; # hashref
Get all cookies.
cookie
my $session = $req->cookie('session');
Get a single cookie value.
BODY METHODS (ASYNC)
body_stream
my $stream = $req->body_stream;
my $stream = $req->body_stream(
max_bytes => 10 * 1024 * 1024, # 10MB limit
decode => 'UTF-8', # Decode to UTF-8
strict => 1, # Strict UTF-8 decoding
);
Returns a PAGI::Request::BodyStream for streaming body consumption. This is useful for processing large request bodies incrementally without loading them entirely into memory.
Options:
max_bytes- Maximum body size. Defaults to Content-Length header if present.decode- Encoding to decode chunks to (typically 'UTF-8').strict- If true, throw on invalid UTF-8. Default: false (use replacement chars).
Important: Body streaming is mutually exclusive with buffered body methods (body, text, json, form). Once you start streaming, you cannot use those methods, and vice versa.
Example:
# Stream large upload to file
my $stream = $req->body_stream(max_bytes => 100 * 1024 * 1024);
await $stream->stream_to_file('/uploads/data.bin');
See PAGI::Request::BodyStream for full documentation.
body
my $bytes = await $req->body;
Read raw body bytes. Cached after first read.
Important: Cannot be used after body_stream() has been called.
text
my $text = await $req->text;
Read body as UTF-8 decoded text.
json
my $data = await $req->json;
Parse body as JSON. Dies on parse error.
form
my $form = await $req->form; # Hash::MultiValue
Parse URL-encoded or multipart form data.
UPLOAD METHODS (ASYNC)
uploads
my $uploads = await $req->uploads; # Hash::MultiValue
Get all uploads as Hash::MultiValue of PAGI::Request::Upload objects.
upload
my $file = await $req->upload('avatar');
Get a single upload by field name.
upload_all
my @files = await $req->upload_all('photos');
Get all uploads for a field name.
PREDICATES
is_get, is_post, is_put, is_patch, is_delete, is_head, is_options
if ($req->is_post) { ... }
Check HTTP method.
is_json
True if Content-Type is application/json.
is_form
True if Content-Type is form-urlencoded or multipart.
is_multipart
True if Content-Type is multipart/form-data.
accepts
if ($req->accepts('text/html')) { ... }
if ($req->accepts('json')) { ... }
Check Accept header (supports wildcards and shortcuts). Returns true if the client accepts the given MIME type.
preferred_type
my $type = $req->preferred_type('json', 'html', 'xml');
Returns the best matching content type from the provided list based on the client's Accept header and quality values. Returns undef if none are acceptable. Supports shortcuts (json, html, xml, etc).
is_disconnected (async)
if (await $req->is_disconnected) { ... }
Check if client has disconnected.
AUTH HELPERS
bearer_token
my $token = $req->bearer_token;
Extract Bearer token from Authorization header.
basic_auth
my ($user, $pass) = $req->basic_auth;
Decode Basic auth credentials.
STASH
stash
$req->stash->{user} = $current_user;
my $user = $req->stash->{user};
Returns the per-request stash hashref for sharing data between middleware and handlers. The stash is also accessible via $res->stash, $ws->stash, and $sse->stash for consistency.
How Stash Works
The stash lives in $scope->{'pagi.stash'}, not in the Request object itself. This is an important design choice:
Scope-based, not object-based - Request/Response objects are ephemeral (each middleware/handler may create its own), but stash persists because it lives in scope.
Survives shallow copies - When middleware creates a modified scope (
{ %$scope, key => val }), the stash hashref is preserved by reference. All objects in the chain see the same stash.Shared across the chain - Middleware sets values, handlers read them, subrouters inherit them. The stash "flows through" via scope sharing.
Example
# In auth middleware
async sub require_auth {
my ($self, $req, $res, $next) = @_;
$req->stash->{user} = verify_token($req->bearer_token);
await $next->();
}
# In handler - sees the user (even though it's a different $req object)
async sub get_profile {
my ($self, $req, $res) = @_;
my $user = $req->stash->{user}; # Set by middleware
await $res->json($user);
}
# Can also read via Response
async sub another_handler {
my ($self, $req, $res) = @_;
my $user = $res->stash->{user}; # Same stash!
await $res->json($user);
}
Note: For worker-level state (database connections, config), use $self->state in PAGI::Endpoint::Router subclasses.
SEE ALSO
PAGI::Request::Upload, PAGI::Request::BodyStream, Hash::MultiValue