Controllers and the context
A controller method is a plain sub receiving one argument: the
Punk::Context. No instance, no dispatch overhead - the coderef was
resolved once at to_app and is called directly per request.
package MyApp::Controller::Web::Book;
use parent 'Punk::Controller';
sub view {
my ($c) = @_;
my $book = $c->model('Book')->get(id => $c->param('id'));
return $c->render('book/view', { book => $book });
}
1;
The context is a blessed array with a fixed slot layout, implemented in C; everything below is an XSUB reading those slots directly.
Parameters
$c->param($name) resolves through layers, most specific first:
validated OpenAPI parameters (path, then query), then route captures,
then the request's query string, then the form body.
$c->params takes the same layers several names at a time. With no
names it is everything merged, stacked in that precedence. With names it
returns a list of values in the order asked for - undef for a name no
layer has - or, in scalar context, a hashref of just the names that were
there, so a set of optional filters is one call:
my ($page, $size) = $c->params(qw(page size));
my %filter = %{ $c->params(qw(state queue task)) };
A repeated parameter (?tag=a&tag=b) is an arrayref.
The request
$c->req is the lazy Punk::Request - nothing is parsed until asked
for, and everything parsed is cached:
$c->req->method; # GET
$c->req->path; # /books/42
$c->req->header('User-Agent');
$c->req->headers; # every header, lowercase-dashed keys
$c->req->query; # parsed query hashref
$c->req->form; # parsed body hashref
$c->req->json; # the body, JSON-decoded
$c->req->body; # the raw bytes
$c->req->cookie('sid');
$c->req->upload('avatar'); # a Punk::Upload from multipart
Responding
$c->json({ ok => 1 }); # application/json
$c->text('plain'); # text/plain
$c->html('<p>markup</p>'); # text/html
$c->render('book/view', \%data); # through the view engine
$c->redirect('/login'); # 302, or pass a status
$c->not_found; # the 404 response
Responses can be shaped before they are sent:
$c->status(201);
$c->header('X-Request-Id' => $id);
$c->cookie(theme => 'dark', expires => '30d');
A handler may also return plain data - it is JSON-encoded with the pending status and headers folded in - or a future that resolves to any of the above; see Async.
The stash
$c->stash is a per-request hashref for passing values between guards,
hooks and the controller:
# in a guard
$c->stash->{user} = $user;
# in the handler
my $user = $c->stash->{user};
Outbound HTTP
$c->ua is a Fetch agent - HTTP/2,
future-based, one per worker so its keep-alive pool survives between
requests. Named agents carry their own options:
ua partner => { timeout => 2 }; # in the app
my $res = $c->await($c->ua('partner')->get('https://api.example.com/'));
Helpers and models
helper installs new context methods (usually from plugins), and
$c->model($name) returns the registered model instance - see
Models.