AnyEvent-HTTPD-Router
view release on metacpan or search on metacpan
lib/AnyEvent/HTTPD/Router.pm view on Meta::CPAN
package AnyEvent::HTTPD::Router;
use common::sense;
use parent 'AnyEvent::HTTPD';
use AnyEvent::HTTPD;
use Carp;
use AnyEvent::HTTPD::Router::DefaultDispatcher;
our $VERSION = '1.0.1';
sub new {
my $this = shift;
my $class = ref($this) || $this;
my %args = @_;
# todo documentation how to overwrite your dispathing
my $dispatcher = delete $args{dispatcher};
my $routes = delete $args{routes};
my $auto_respond_404 = delete $args{auto_respond_404};
my $dispatcher_class = delete $args{dispatcher_class}
|| 'AnyEvent::HTTPD::Router::DefaultDispatcher';
my $known_methods = delete $args{known_methods}
|| [ qw/GET HEAD POST PUT PATCH DELETE TRACE OPTIONS CONNECT/ ];
my $self = $class->SUPER::new(%args);
$self->{known_methods} = $known_methods;
$self->{dispatcher} = defined $dispatcher
? $dispatcher
: $dispatcher_class->new();
$self->reg_cb(
'request' => sub {
my $self = shift;
my $req = shift;
my $matched = $self->dispatcher->match( $self, $req );
unless ($matched) {
$self->event( 'no_route_found' => $req );
}
},
);
$self->reg_cb('no_route_found' => sub {
my ( $httpd, $req ) = @_;
$req->respond( [ 404, 'not found', {}, '' ] );
}) if $auto_respond_404;
if ($routes) {
$self->reg_routes( @$routes );
}
return $self;
}
sub dispatcher { shift->{dispatcher} }
sub _check_verb {
my $self = shift;
my $verb = shift;
my $methods = shift;
if ( $verb =~ m/^:/ ) {
$methods->{$_}++ for qw(GET POST); # convert ':verbs' to POST and GET
return 1;
} elsif ( grep { $verb eq $_ } @{ $self->{known_methods} } ) {
$methods->{$verb}++;
return 1;
}
return;
}
sub reg_routes {
my $self = shift;
croak 'arguments to reg_routes are required' if @_ == 0;
croak 'arguments to reg_routes are confusing' if @_ % 3 != 0;
# * mix allowed methods and new http methods together
my %methods = map { $_ => 1 } @{ $self->allowed_methods };
while (my ($verbs, $path, $cb) = splice(@_, 0, 3) ) {
$verbs = ref($verbs) eq 'ARRAY'
? $verbs
: [ $verbs ];
if ( not ref($cb) eq 'CODE' ) {
croak 'callback must be a coderef';
}
elsif ( not $path =~ m/^\// ) {
croak 'path syntax is wrong';
}
foreach my $verb (@$verbs) {
croak 'verbs or methods are wrong'
unless $self->_check_verb( $verb, \%methods );
}
$self->dispatcher->add_route($verbs, $path, $cb);
}
# set allowed methods new
# Todo: setter doesnt work in this AE::HTTPD version
# so must do push(@{$self->{allowed_methods}}
# later we can do setter if AE::HTTPD version is high enough
$self->{allowed_methods} = [ sort keys %methods ];
}
1;
__END__
=encoding utf-8
=head1 NAME
AnyEvent::HTTPD::Router - Adding Routes to AnyEvent::HTTPD
=head1 DESCRIPTION
AnyEvent::HTTPD::Router is an extension to the L<AnyEvent::HTTPD> module, from
which it is inheriting. It adds the C<reg_routes()> method to it.
This module aims to add as little as possible overhead to it while still being
flexible and extendable. It requires the same little dependencies that
L<AnyEvent::HTTPD> uses.
The dispatching for the routes happens first. If no route could be found, or you
do not stop further dispatching with C<stop_request()> the registered callbacks
will be executed as well; as if you would use L<AnyEvent::HTTPD>. In other
words, if you plan to use routes in your project you can use this module and
upgrade from callbacks to routes step by step.
Routes support http methods, but custom methods
L<https://cloud.google.com/apis/design/custom_methods> can also be used. You
don't need to, of course ;-)
=head1 SYNOPSIS
use AnyEvent::HTTPD::Router;
my $httpd = AnyEvent::HTTPD::Router->new( port => 1337 );
my $all_methods = [qw/GET DELETE HEAD POST PUT PATCH/];
$httpd->reg_routes(
GET => '/index.txt' => sub {
my ( $httpd, $req ) = @_;
$httpd->stop_request;
$req->respond([
200, 'ok', { 'Content-Type' => 'text/plain', }, "test!" ]);
},
$all_methods => '/my-method' => sub {
my ( $httpd, $req ) = @_;
$httpd->stop_request;
$req->respond([
200, 'ok', { 'X-Your-Method' => $req->method }, '' ]);
},
GET => '/calendar/:year/:month/:day' => sub {
my ( $httpd, $req, $param ) = @_;
my $calendar_entries = get_cal_entries(
$param->{year}, $param->{month}, $param->{day}
);
$httpd->stop_request;
$reg->respond([
200, 'ok', { 'Content-Type' => 'application/json'},
to_json($calendar_entries)
]);
},
GET => '/static-files/*' => sub {
my ( $httpd, $req, $param ) = @_;
my $requeted_file = $param->{'*'};
my ($content, $content_type) = black_magic($requested_file);
$httpd->stop_request;
$req->respond([
200, 'ok', { 'Content-Type' => $content_type }, $content ]);
}
);
$httpd->run();
=head1 METHODS
=over
=item * C<new()>
Creates a new C<AnyEvent::HTTPD::Router> server. The constructor handles the
following parameters. All further parameters are passed to C<AnyEvent::HTTPD>.
=over
=item * C<dispatcher>
You can pass your own implementation of your router dispatcher into this module.
This expects the dispatcher to be an instance not a class name.
=item * C<dispatcher_class>
You can pass your own implementation of your router dispatcher into this module.
This expects the dispatcher to be a class name.
( run in 1.172 second using v1.01-cache-2.11-cpan-97f6503c9c8 )