NAME

PSGI::Handy - a tiny dependency-free PSGI web framework for Perl 5.005_03 and later

VERSION

Version 0.01

SYNOPSIS

use PSGI::Handy;

my $app = PSGI::Handy->new(
    renderer => \&my_template_renderer,   # or an object with render()
    db       => $dbh,                     # any database handle
);

$app->get('/', sub {
    my $c = shift;
    return $c->html('<h1>Hello</h1>');
});

$app->get('/users/:id', sub {
    my $c = shift;
    return $c->render('user.html', { id => $c->param('id') });
});

$app->post('/users', sub {
    my $c = shift;
    my $name = $c->param('name');
    # ... use $c->db ...
    return $c->redirect('/');
});

# PSGI::Handy builds the PSGI app; serve it with any PSGI server:
my $psgi_app = $app->to_app;   # sub { my $env = shift; ... }

# for example, with HTTP::Handy as the delivery layer:
use HTTP::Handy;
HTTP::Handy->run(app => $psgi_app, host => '127.0.0.1', port => 8080);

TABLE OF CONTENTS

DESCRIPTION

PSGI::Handy is the application layer of the "Handy" stack. It wires a router ("PSGI::Handy::Router"), a request and a response object ("PSGI::Handy::Request", "PSGI::Handy::Response") and a per-request context ("PSGI::Handy::Context") into a single PSGI-subset $app through to_app. Templates and a database handle are injected at construction time, so the framework loads nothing outside the Perl core and stays easy to test. You serve the resulting $app with any PSGI server, such as HTTP::Handy.

All four component classes are defined in this one file; there are no separate PSGI/Handy/*.pm files to install or to keep in version step. Their module names are still loadable (use PSGI::Handy::Router is a no-op once PSGI::Handy is loaded), and each is documented in its own section below so that perldoc PSGI::Handy covers the whole framework.

Every handler receives a context ("PSGI::Handy::Context") and may return a Response object, a raw PSGI array reference, or a plain string (treated as an HTML 200 response).

METHODS

These are the methods of the PSGI::Handy application object:

new, renderer, db, config, router, route, get, post, put, patch, del, head, any, before, after, to_app.

Routing details (named :params, trailing *, exact matching, 405 handling) are documented under "PSGI::Handy::Router".

DIAGNOSTICS

The framework dies (via Carp::croak) with these messages. They are collected here for the whole single-file distribution.

before: a code reference is required

The argument to before was not a CODE reference.

after: a code reference is required

The argument to after was not a CODE reference.

new: a PSGI env hash reference is required

PSGI::Handy::Request::new was called without a PSGI environment hash reference.

add: method is required

PSGI::Handy::Router::add was called without an HTTP method.

add: pattern is required

PSGI::Handy::Router::add was called without a path pattern.

add: handler must be a code reference

The handler passed to PSGI::Handy::Router::add was not a CODE reference.

add: pattern must begin with '/' (got '$pattern')

The path pattern passed to PSGI::Handy::Router::add did not begin with a slash.

match: method is required

PSGI::Handy::Router::match was called without an HTTP method.

match: path is required

PSGI::Handy::Router::match was called without a path.

redirect: location is required

PSGI::Handy::Response::redirect was called without a target location.

json: body must be a pre-encoded JSON string, not a reference

PSGI::Handy::Response::json was given a reference (array or hash). The body must be a JSON string that the caller has already encoded (for example with mb::JSON).

cookie: name is required

PSGI::Handy::Response::cookie was called without a cookie name.

render: no renderer configured (pass renderer = ... to PSGI::Handy->new)>

render was called but no renderer was injected into the application.

render: renderer must be a code reference or an object with a render() method

The configured renderer is neither a CODE reference nor an object with a render method.

LIMITATIONS

The $app returned by to_app always produces the buffered, three-element PSGI response [ $status, \@headers, \@body ]. The PSGI delayed-response form (the streaming "responder" callback) is not generated; this is what "PSGI-subset" means throughout this distribution.

Concurrency and the HTTP version depend on the PSGI server you choose. No multipart uploads or WebSocket in this version. HEAD requests are served by the matching GET route with the body removed.

PSGI::Handy::Router

A tiny PSGI route dispatcher. It resolves an incoming request, expressed as an HTTP method and a PATH_INFO string, to a previously registered handler.

Pattern syntax

  • Literal segments match exactly. A dot is a literal dot, not a regular-expression wildcard (/feed.xml does not match /feedaxml).

  • :name matches a single non-empty path segment ([^/]+) and stores it in params under name.

  • A * used as the final segment matches the remainder of the path, including slashes, and is stored under splat.

Matching is exact (anchored), so a trailing slash is significant: /a and /a/ are different routes.

Router methods

new

Returns a new, empty router.

add($method, $pattern, $handler)

Registers a route. $handler must be a code reference. $pattern must begin with /. Returns the router for chaining.

match($method, $path)

Returns a hash reference { handler => ..., params => ... } on success, { allowed => [...] } when the path is known but the method is not (HTTP 405), or undef when nothing matched (HTTP 404). The first registered matching route wins.

routes

Returns the internal array reference of route records. For introspection and testing.

Path parameters rely on positional captures paired with a name list because named captures were not available until Perl 5.10.

PSGI::Handy::Request

A tiny PSGI environment wrapper. It provides convenient read access to the request. Query-string and application/x-www-form-urlencoded body parameters are parsed in pure Perl and merged (body values appended after query values); multi-value fields are available through param_all. multipart/form-data is not parsed in this version; use body for the raw payload.

Request methods

new, method, path, query_string, content_type, content_length, env, header, body, param, param_all, param_names, params, cookie, cookies.

PSGI::Handy::Response

A tiny PSGI response builder. finalize returns the PSGI three-element array a PSGI-subset server such as HTTP::Handy expects. Content-Length is computed from the body at finalize time unless the caller already set it. Bodies are expected to be byte strings already in the desired encoding; the class does no character encoding itself.

Response methods

new, html, text, json, redirect, status, set_status, body, set_body, header, set_header, remove_header, content_type, cookie, finalize. Mutators return the object for chaining; for example:

my $res = PSGI::Handy::Response->new;
$res->set_status(201)
    ->content_type('text/html; charset=utf-8')
    ->header('X-App', 'PSGI::Handy')
    ->cookie('sid', $id, path => '/', httponly => 1)
    ->set_body($html);
return $res->finalize;   # [ 201, [...], [ $html ] ]

PSGI::Handy::Context

The per-request context handed to every route handler as its single argument. It exposes the request (req), matched path parameters (param, params), a per-request stash, the injected database handle (db), configuration (config), response builders (html, text, json, redirect, res), and template rendering (render).

Context methods

new, req, app, params, param, db, config, stash, html, text, json, redirect, res, render.

SEE ALSO

HTTP::Handy, HP::Handy, DB::Handy.

AUTHOR

INABA Hitoshi <ina.cpan@gmail.com>

COPYRIGHT AND LICENSE

This software is distributed under the same terms as Perl itself.