Leyland
view release on metacpan or search on metacpan
lib/Leyland/Negotiator.pm view on Meta::CPAN
sub negotiate {
my ($class, $c, $app_routes, $path) = @_;
# 1. CHARACTER SET NEGOTIATION
# --------------------------------------------------------------
# Leyland only supports UTF-8 character encodings, so let's check
# the client supports that. If not, let's return an error
$c->log->debug('Negotiating character set.');
Leyland::Negotiator->_negotiate_charset($c)
|| $c->exception({ code => 400, error => "This server only supports the UTF-8 character set, unfortunately we are unable to fulfil your request." });
# 2. PATH NEGOTIATION
# --------------------------------------------------------------
# let's find all possible prefix/route combinations
# from the request path, and then find all routes matching
# the request path
my $routes = [];
$path ||= $c->path;
$routes = $class->_negotiate_path($c, { app_routes => $app_routes, path => $path });
$c->exception({ code => 404 }) unless scalar @$routes;
$c->log->debug('Found '.scalar(@$routes).' routes matching '.$path);
# 3. REQUEST METHOD NEGOTIATION
# --------------------------------------------------------------
# weed out routes that do not match request method
$c->log->debug('Negotiating request method.');
$routes = $class->_negotiate_method($c->method, $routes);
$c->exception({ code => 405 }) unless scalar @$routes;
# 4. RECEIVED CONTENT TYPE NEGOTIATION
# --------------------------------------------------------------
# weed out all routes that do not accept the media type that the
# client used for the request
$c->log->debug('Negotiating media type received.');
$routes = $class->_negotiate_receive_media($c, $routes);
$c->exception({ code => 415 }) unless scalar @$routes;
# 5. RETURNED CONTENT TYPE NEGOTIATION
# --------------------------------------------------------------
# weed out all routes that do not return any media type
# the client accepts
$c->log->debug('Negotiating media type returned.');
$routes = $class->_negotiate_return_media($c, $routes);
$c->exception({ code => 406 }) unless scalar @$routes;
return $routes;
}
=head2 find_options( $c, $app_routes )
Finds all routes that match a certain path when an HTTP OPTIONS request
is received.
=cut
sub find_options {
my ($class, $c, $app_routes) = @_;
my $routes = $class->matching_routes($app_routes, $class->prefs_and_routes($c->path));
# have we found any matching routes?
$c->exception({ code => 404 }) unless scalar @$routes;
# okay, we have, let's see which HTTP methods are supported by
# these routes
my %meths = ( 'OPTIONS' => 1 );
foreach (@$routes) {
$meths{$class->method_name($_->{method})} = 1;
}
return sort keys %meths;
}
=head2 method_name( $meth )
Receives the name of a Leyland-style HTTP method (like 'get', 'post',
'put' or 'del') and returns the correct HTTP name of it (like 'GET', 'POST',
'PUT' or 'DELETE').
=cut
sub method_name {
my ($class, $meth) = @_;
# replace 'del' with 'delete'
$meth = 'delete' if $meth eq 'del';
# return this in uppercase
return uc($meth);
}
sub _negotiate_path {
my ($class, $c, $args) = @_;
$args->{path} ||= $c->path;
# let's find all possible prefix/route combinations
# from the request path and then find all routes matching the request path
my $routes = $class->_matching_routes($args->{app_routes}, $class->_prefs_and_routes($args->{path}), $args->{internal});
if ($args->{method}) {
return $class->_negotiate_method($args->{method}, $routes);
} else {
return $routes;
}
}
sub _prefs_and_routes {
my ($class, $path) = @_;
my $pref_routes = [{ prefix => '', route => $path }];
my ($prefix) = ($path =~ m!^(/[^/]+)!);
my $route = $' || '/';
my $i = 0; # counter to prevent infinite loops, probably should be removed
while ($prefix && $i < 1000) {
push(@$pref_routes, { prefix => $prefix, route => $route });
my ($suffix) = ($route =~ m!^(/[^/]+)!);
last unless $suffix;
$prefix .= $suffix;
$route = $' || '/';
$i++;
}
return $pref_routes;
}
sub _matching_routes {
my ($class, $app_routes, $pref_routes, $internal) = @_;
my $routes = [];
foreach (@$pref_routes) {
my $pref_name = $_->{prefix} || '_root_';
next unless $app_routes->EXISTS($pref_name);
my $pref_routes = $app_routes->FETCH($pref_name);
next unless $pref_routes;
# find matching routes in this prefix
ROUTE: foreach my $r ($pref_routes->Keys) {
# does the requested route match the current route?
next unless my @captures = ($_->{route} =~ m/$r/);
shift @captures if scalar @captures == 1 && $captures[0] eq '1';
my $route_meths = $pref_routes->FETCH($r);
# find all routes that support the request method (i.e. GET, POST, etc.)
METH: foreach my $m (sort { $a eq 'any' || $b eq 'any' } keys %$route_meths) {
# do not match internal routes
RULE: foreach my $rule (@{$route_meths->{$m}->{rules}->{is} || []}) {
next METH if $rule eq 'internal' && !$internal;
}
# okay, add this route
push(@$routes, { method => $m, class => $route_meths->{$m}->{class}, prefix => $_->{prefix}, route => $r, code => $route_meths->{$m}->{code}, rules => $route_meths->{$m}->{rules}, captures => \@captures });
}
}
}
return $routes;
}
sub _negotiate_method {
my ($class, $method, $routes) = @_;
( run in 2.672 seconds using v1.01-cache-2.11-cpan-302cb4679cc )