Name
Router::Resource - Build REST-inspired routing tables
Synopsis
use Router::Resource;
use Plack::Builder;
use namespace::autoclean;
sub app {
# Create a routing table.
my $router = router {
resource '/' => sub {
GET { $template->render('home') };
};
resource '/blog/{year}/{month}' => sub {
GET { [200, [], [ $template->render({ posts => \@posts }) ] };
POST { push @posts, new_post(shift); [200, [], ['ok']] };
};
};
# Build the Plack app to use it.
builder {
sub { $router->dispatch(shift) };
};
}
Description
There are a bunch of path routers on CPAN, but they tend not to be very RESTy. A basic idea of a RESTful API is that URIs point to resources and the standard HTTP methods indicate the actions to be taken on those resources. So to encourage you to think about it that way, Router::Resource requires that you declare resources and then the HTTP methods that are implemented for those resources.
The rules for matching paths are defined by Router::Simple's routing rules, which offer quite a lot of flexibility.
Interface
You create a router in a router
block. Within that block, define resources understood by the router with the resource
keyword, which takes a resource path and a block defining its interface:
my $router = {
resource '/' => sub { [[200, [], ['ok']] };
resource '/foo' => sub { [[200, [], ['ok']] };
};
Within a resource block, declare the HTTP methods that the resource responds to by using one or more of the following keywords:
GET
HEAD
POST
PUT
DELETE
OPTIONS
TRACE
CONNECT
Note that if you define a GET
method but not a HEAD
method, the GET
method will respond to HEAD
requests.
These methods should expect two arguments: the matched request (generally a PSGI $env
hash) and a hash of the matched data as created by Router::Simple. For example, in a Plack-powered Wiki app you might do something like this:
resource '/wiki/{name}' => sub {
GET {
my $req = Plack::Request->new(shift);
my $params = shift;
my $wiki = Wiki->lookup( $parmas->{name} );
my $res = $req->new_response;
$res->content_type('text/html; charset=UTF-8');
$res->body($wiki);
return $res->finalize;
};
};
But of course you can abstract that into a controller or other code that the HTTP method simply dispatches to.
If you wish the router to create an OPTIONS
handler for you, pass the auto_options
parameter to router
:
$router = router {
resource '/blog/{year}/{month}' => sub {
GET { [200, [], [ $template->render({ posts => \@posts }) ] };
POST { push @posts, new_post(shift); [200, [], ['ok']] };
};
} auto_options => 1;
With auto_options
enabled, Router::Resource will look at the methods defined for a resource to define the OPTIONS
handler. In this example, $router
's OPTIONS
method will specify that GET
, HEAD
, and OPTIONS
are valid for /blog/{year}/{month}
.
Dispatching
Use the dispatch
method to have the router dispatch HTTP requests. For a Plack app, it looks something like this:
sub { $router->dispatch(shift) };
The assumption is that the methods you've defined will return a PSGI-compatible array reference. When the router finds no matching resource or method, such an array is precisely what it will return. When a resource cannot be found, it will return
[404, [], ['not found']]
If the resource is found but the requested method is not defined, it returns something like:
[405, [Allow => 'GET, HEAD'], ['not allowed']]
The "Allow" header will list the methods that the requested resource does respond to.
Of course you may not want something so simple for your app. So use the missing
keyword to specify a code block to handle this situation. The code block should expect two arguments: the unmatched request $env
hash and a hash describing the failure. For an unfound resource, that hash will contain:
{ code => 404, message => 'not found', headers => [] }
If a resource was found but it does not define the requested method, the hash will look something like this:
{ code => 405, message => 'not allowed', headers => [Allow => 'GET, HEAD'] }
This is designed to make it relatively easy to create a custom response to unfound resources and missing methods. Something like:
missing {
my $req = Plack::Request->new(shift);
my $params = shift;
my $res = $req->new_response($params->{code});
$res->headers(@{ $params->{headers} });
$res->content_type('text/html; charset=UTF-8');
$res->body($template->show('not_found', $params));
return $res->finalize;
};
See Also
Router::Simple provides the rule syntax for Router::Resource resource paths.
Router::Simple::Sinatraish provides a Sinatraish routing table interface. It's nice, though perhaps a bit too magical.
Sinatra::Resources - The Ruby module that inspired this module.
Plack is the way to write your Perl web apps. Router::Resource is fully Plack-aware.
Support
This module is stored in an open GitHub repository. Feel free to fork and contribute!
Please file bug reports via GitHub Issues or by sending mail to bug-Router-Resource@rt.cpan.org.
Author
David E. Wheeler <david@kineticode.com>
Acknowledgements
My thanks to the denizens of #plack for their feedback and advice on this module, including:
Copyright and License
Copyright (c) 2010-2011 David E. Wheeler. Some Rights Reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.