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 | 1 | 115µs | }); # spent 115µs making 1 call to Plack::Component::to_app_auto | ||
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 |