| Filename | /usr/share/perl5/Plack/App/URLMap.pm |
| Statements | Executed 0 statements in 0s |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | package Plack::App::URLMap; | ||||
| 2 | use strict; | ||||
| 3 | use warnings; | ||||
| 4 | use parent qw(Plack::Component); | ||||
| 5 | use constant DEBUG => $ENV{PLACK_URLMAP_DEBUG}; | ||||
| 6 | |||||
| 7 | use Carp (); | ||||
| 8 | |||||
| 9 | sub mount { shift->map(@_) } | ||||
| 10 | |||||
| 11 | sub map { | ||||
| 12 | my $self = shift; | ||||
| 13 | my($location, $app) = @_; | ||||
| 14 | |||||
| 15 | my $host; | ||||
| 16 | if ($location =~ m!^https?://(.*?)(/.*)!) { | ||||
| 17 | $host = $1; | ||||
| 18 | $location = $2; | ||||
| 19 | } | ||||
| 20 | |||||
| 21 | if ($location !~ m!^/!) { | ||||
| 22 | Carp::croak("Paths need to start with /"); | ||||
| 23 | } | ||||
| 24 | $location =~ s!/$!!; | ||||
| 25 | |||||
| 26 | push @{$self->{_mapping}}, [ $host, $location, qr/^\Q$location\E/, $app ]; | ||||
| 27 | } | ||||
| 28 | |||||
| 29 | sub prepare_app { | ||||
| 30 | my $self = shift; | ||||
| 31 | # sort by path length | ||||
| 32 | $self->{_sorted_mapping} = [ | ||||
| 33 | map { [ @{$_}[2..5] ] } | ||||
| 34 | sort { $b->[0] <=> $a->[0] || $b->[1] <=> $a->[1] } | ||||
| 35 | map { [ ($_->[0] ? length $_->[0] : 0), length($_->[1]), @$_ ] } @{$self->{_mapping}}, | ||||
| 36 | ]; | ||||
| 37 | } | ||||
| 38 | |||||
| 39 | sub call { | ||||
| 40 | my ($self, $env) = @_; | ||||
| 41 | |||||
| 42 | my $path_info = $env->{PATH_INFO}; | ||||
| 43 | my $script_name = $env->{SCRIPT_NAME}; | ||||
| 44 | |||||
| 45 | my($http_host, $server_name) = @{$env}{qw( HTTP_HOST SERVER_NAME )}; | ||||
| 46 | |||||
| 47 | if ($http_host and my $port = $env->{SERVER_PORT}) { | ||||
| 48 | $http_host =~ s/:$port$//; | ||||
| 49 | } | ||||
| 50 | |||||
| 51 | for my $map (@{ $self->{_sorted_mapping} }) { | ||||
| 52 | my($host, $location, $location_re, $app) = @$map; | ||||
| 53 | my $path = $path_info; # copy | ||||
| 54 | no warnings 'uninitialized'; | ||||
| 55 | DEBUG && warn "Matching request (Host=$http_host Path=$path) and the map (Host=$host Path=$location)\n"; | ||||
| 56 | next unless not defined $host or | ||||
| 57 | $http_host eq $host or | ||||
| 58 | $server_name eq $host; | ||||
| 59 | next unless $location eq '' or $path =~ s!$location_re!!; | ||||
| 60 | next unless $path eq '' or $path =~ m!^/!; | ||||
| 61 | DEBUG && warn "-> Matched!\n"; | ||||
| 62 | |||||
| 63 | my $orig_path_info = $env->{PATH_INFO}; | ||||
| 64 | my $orig_script_name = $env->{SCRIPT_NAME}; | ||||
| 65 | |||||
| 66 | $env->{PATH_INFO} = $path; | ||||
| 67 | $env->{SCRIPT_NAME} = $script_name . $location; | ||||
| 68 | return $self->response_cb($app->($env), sub { | ||||
| 69 | $env->{PATH_INFO} = $orig_path_info; | ||||
| 70 | $env->{SCRIPT_NAME} = $orig_script_name; | ||||
| 71 | 5 | 153µs | }); # spent 153µs making 5 calls to Plack::Component::to_app_auto, avg 31µs/call | ||
| 72 | } | ||||
| 73 | |||||
| 74 | DEBUG && warn "All matching failed.\n"; | ||||
| 75 | |||||
| 76 | return [404, [ 'Content-Type' => 'text/plain' ], [ "Not Found" ]]; | ||||
| 77 | } | ||||
| 78 | |||||
| 79 | 1; | ||||
| 80 | |||||
| 81 | __END__ | ||||
| 82 | |||||
| 83 | =head1 NAME | ||||
| 84 | |||||
| 85 | Plack::App::URLMap - Map multiple apps in different paths | ||||
| 86 | |||||
| 87 | =head1 SYNOPSIS | ||||
| 88 | |||||
| 89 | use Plack::App::URLMap; | ||||
| 90 | |||||
| 91 | my $app1 = sub { ... }; | ||||
| 92 | my $app2 = sub { ... }; | ||||
| 93 | my $app3 = sub { ... }; | ||||
| 94 | |||||
| 95 | my $urlmap = Plack::App::URLMap->new; | ||||
| 96 | $urlmap->map("/" => $app1); | ||||
| 97 | $urlmap->map("/foo" => $app2); | ||||
| 98 | $urlmap->map("http://bar.example.com/" => $app3); | ||||
| 99 | |||||
| 100 | my $app = $urlmap->to_app; | ||||
| 101 | |||||
| 102 | =head1 DESCRIPTION | ||||
| 103 | |||||
| 104 | Plack::App::URLMap is a PSGI application that can dispatch multiple | ||||
| 105 | applications based on URL path and host names (a.k.a "virtual hosting") | ||||
| 106 | and takes care of rewriting C<SCRIPT_NAME> and C<PATH_INFO> (See | ||||
| 107 | L</"HOW THIS WORKS"> for details). This module is inspired by | ||||
| 108 | Ruby's Rack::URLMap. | ||||
| 109 | |||||
| 110 | =head1 METHODS | ||||
| 111 | |||||
| 112 | =over 4 | ||||
| 113 | |||||
| 114 | =item map | ||||
| 115 | |||||
| 116 | $urlmap->map("/foo" => $app); | ||||
| 117 | $urlmap->map("http://bar.example.com/" => $another_app); | ||||
| 118 | |||||
| 119 | Maps URL path or an absolute URL to a PSGI application. The match | ||||
| 120 | order is sorted by host name length and then path length (longest strings | ||||
| 121 | first). | ||||
| 122 | |||||
| 123 | URL paths need to match from the beginning and should match completely | ||||
| 124 | until the path separator (or the end of the path). For example, if you | ||||
| 125 | register the path C</foo>, it I<will> match with the request C</foo>, | ||||
| 126 | C</foo/> or C</foo/bar> but it I<won't> match with C</foox>. | ||||
| 127 | |||||
| 128 | Mapping URLs with host names is also possible, and in that case the URL | ||||
| 129 | mapping works like a virtual host. | ||||
| 130 | |||||
| 131 | Mappings will nest. If $app is already mapped to C</baz> it will | ||||
| 132 | match a request for C</foo/baz> but not C</foo>. See L</"HOW THIS | ||||
| 133 | WORKS"> for more details. | ||||
| 134 | |||||
| 135 | =item mount | ||||
| 136 | |||||
| 137 | Alias for C<map>. | ||||
| 138 | |||||
| 139 | =item to_app | ||||
| 140 | |||||
| 141 | my $handler = $urlmap->to_app; | ||||
| 142 | |||||
| 143 | Returns the PSGI application code reference. Note that the | ||||
| 144 | Plack::App::URLMap object is callable (by overloading the code | ||||
| 145 | dereference), so returning the object itself as a PSGI application | ||||
| 146 | should also work. | ||||
| 147 | |||||
| 148 | =back | ||||
| 149 | |||||
| 150 | =head1 PERFORMANCE | ||||
| 151 | |||||
| 152 | If you C<map> (or C<mount> with Plack::Builder) N applications, | ||||
| 153 | Plack::App::URLMap will need to at most iterate through N paths to | ||||
| 154 | match incoming requests. | ||||
| 155 | |||||
| 156 | It is a good idea to use C<map> only for a known, limited amount of | ||||
| 157 | applications, since mounting hundreds of applications could affect | ||||
| 158 | runtime request performance. | ||||
| 159 | |||||
| 160 | =head1 DEBUGGING | ||||
| 161 | |||||
| 162 | You can set the environment variable C<PLACK_URLMAP_DEBUG> to see how | ||||
| 163 | this application matches with the incoming request host names and | ||||
| 164 | paths. | ||||
| 165 | |||||
| 166 | =head1 HOW THIS WORKS | ||||
| 167 | |||||
| 168 | This application works by I<fixing> C<SCRIPT_NAME> and C<PATH_INFO> | ||||
| 169 | before dispatching the incoming request to the relocated | ||||
| 170 | applications. | ||||
| 171 | |||||
| 172 | Say you have a Wiki application that takes C</index> and C</page/*> | ||||
| 173 | and makes a PSGI application C<$wiki_app> out of it, using one of | ||||
| 174 | supported web frameworks, you can put the whole application under | ||||
| 175 | C</wiki> by: | ||||
| 176 | |||||
| 177 | # MyWikiApp looks at PATH_INFO and handles /index and /page/* | ||||
| 178 | my $wiki_app = sub { MyWikiApp->run(@_) }; | ||||
| 179 | |||||
| 180 | use Plack::App::URLMap; | ||||
| 181 | my $app = Plack::App::URLMap->new; | ||||
| 182 | $app->mount("/wiki" => $wiki_app); | ||||
| 183 | |||||
| 184 | When a request comes in with C<PATH_INFO> set to C</wiki/page/foo>, | ||||
| 185 | the URLMap application C<$app> strips the C</wiki> part from | ||||
| 186 | C<PATH_INFO> and B<appends> that to C<SCRIPT_NAME>. | ||||
| 187 | |||||
| 188 | That way, if the C<$app> is mounted under the root | ||||
| 189 | (i.e. C<SCRIPT_NAME> is C<"">) with standalone web servers like | ||||
| 190 | L<Starman>, C<SCRIPT_NAME> is now locally set to C</wiki> and | ||||
| 191 | C<PATH_INFO> is changed to C</page/foo> when C<$wiki_app> gets called. | ||||
| 192 | |||||
| 193 | =head1 AUTHOR | ||||
| 194 | |||||
| 195 | Tatsuhiko Miyagawa | ||||
| 196 | |||||
| 197 | =head1 SEE ALSO | ||||
| 198 | |||||
| 199 | L<Plack::Builder> | ||||
| 200 | |||||
| 201 | =cut |