File Coverage

lib/UR/Service/UrlRouter.pm
Criterion Covered Total %
statement 39 40 97.5
branch 11 12 91.6
condition 10 10 100.0
subroutine 10 10 100.0
pod n/a
total 70 72 97.2


line stmt bran cond sub pod time code
1             package UR::Service::UrlRouter;
2              
3 1     1   56 use strict;
  1         1  
  1         29  
4 1     1   3 use warnings;
  1         1  
  1         27  
5 1     1   3 use UR;
  1         1  
  1         10  
6              
7 1     1   3 use Sub::Install;
  1         1  
  1         7  
8              
9             use overload '&{}' => \&__call__, # To support being called as a code ref
10 1     1   43 'bool' => sub { 1 }; # Required due to an unless() test in UR::Context
  1     3   0  
  1         12  
  3         66  
11              
12             class UR::Service::UrlRouter {
13             has_optional => [
14             verbose => { is => 'Boolean' },
15             ]
16             };
17              
18             foreach my $method ( qw( GET POST PUT DELETE ) ) {
19             my $code = sub {
20 7     7   1104 my($self, $path, $sub) = @_;
21              
22 7   100     46 my $list = $self->{$method} ||= [];
23 7         16 push @$list, [ $path, $sub ];
24             };
25             Sub::Install::install_sub({
26             as => $method,
27             code => $code,
28             });
29             }
30              
31             sub _log {
32 13     13   12 my $self = shift;
33 13 50       28 return unless $self->verbose;
34 0         0 print STDERR join("\t", @_),"\n";
35             }
36              
37             sub __call__ {
38 13     13   13395 my $self = shift;
39              
40             return sub {
41 13     13   12 my $env = shift;
42              
43 13         15 my $req_method = $env->{REQUEST_METHOD};
44 13   100     34 my $matchlist = $self->{$req_method} || [];
45              
46 13         20 foreach my $route ( @$matchlist ) {
47 13         14 my($path,$cb) = @$route;
48 8         14 my $call = sub { my $rv = $cb->($env, @_);
49 8         33 $self->_log(200, $req_method, $env->{PATH_INFO}, $path);
50 8 100       47 return ref($rv) ? $rv : [ 200, [], [$rv] ];
51 13         27 };
52              
53 13 100       45 if (my $ref = ref($path)) {
    100          
54 5 100 100     54 if ($ref eq 'Regexp' and (my @matches = $env->{PATH_INFO} =~ $path)) {
    100 100        
55 2         4 return $call->(@matches);
56             } elsif ($ref eq 'CODE' and $path->($env)) {
57 1         7 return $call->();
58             }
59             } elsif ($env->{PATH_INFO} eq $path) {
60 5         9 return $call->();
61             }
62             }
63 5         16 $self->_log(404, $req_method, $env->{PATH_INFO});
64 5         15 return [ 404, [ 'Content-Type' => 'text/plain' ], [ 'Not Found' ] ];
65             }
66 13         58 }
67              
68             1;
69              
70             =pod
71              
72             =head1 NAME
73              
74             UR::Service::UrlRouter - PSGI-aware router for incoming requests
75              
76             =head1 SYNOPSIS
77              
78             my $r = UR::Service::UrlRouter->create();
79             $r->GET('/index.html', \&handle_index);
80             $r->POST(qr(update/(.*?).html, \&handle_update);
81              
82             my $s = UR::Service::WebServer->create();
83             $s->run( $r );
84              
85             =head1 DESCRIPTION
86              
87             This class acts as a middleman, routing requests from a PSGI server to the
88             appropriate function to handle the requests.
89              
90             =head2 Properties
91              
92             =over 4
93              
94             =item verbose
95              
96             If verbose is true, the object will print details about the handled requests
97             to STDOUT.
98              
99             =back
100              
101             =head2 Methods
102              
103             =over 4
104              
105             =item $r->GET($URLish, $handler)
106              
107             =item $r->POST($URLish, $handler)
108              
109             =item $r->PUT($URLish, $handler)
110              
111             =item $r->DELETE($URLisn, $handler)
112              
113             These four methods register a handler for the given request method + URL pair.
114             The first argument specifies the URL to match against, It can be specified
115             in one of the following ways
116              
117             =over 4
118              
119             =item $string
120              
121             A simple string matches the incoming request if the request's path is eq to
122             the $string
123              
124             =item qr(some regex (with) captures)
125              
126             A regex matches the incoming request if the path matches the regex. If the
127             regex contains captures, these are passed as additional arguments to the
128             $handler.
129              
130             =item $coderef
131              
132             A coderef matches the incoming request if $coderef returns true. $coderef
133             is given one acgument: the PSGI env hashref.
134              
135             $handler is a CODE ref. When called, the first argument is the standard PSGI
136             env hashref.
137              
138             =back
139              
140             =item $r->__call__
141              
142             __call__ is not intended to be called directly.
143              
144             This class overloads the function dereference (call) operator so that the
145             object may be used as a callable object (ie. $obj->(arg, arg)). As overload
146             expects, __call__ returns a code ref that handles the PSGI request by finding
147             an appropriate match with the incoming request and a previously registered
148             handler. If no matching handler is found, it returns a 404 error code.
149              
150             If multiple handlers match the incoming request, then only the earliest
151             registered handler will be called.
152              
153             =back
154              
155             =head1 SEE ALSO
156              
157             L, L, L
158              
159             =cut