File Coverage

blib/lib/Apache2/Controller/Dispatch.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package Apache2::Controller::Dispatch;
2              
3             =head1 NAME
4              
5             Apache2::Controller::Dispatch - dispatch base class for Apache::Controller
6              
7             =head1 VERSION
8              
9             Version 1.001.001
10              
11             =cut
12              
13 2     2   3163 use version;
  2         3  
  2         16  
14             our $VERSION = version->new('1.001.001');
15              
16             =head1 SYNOPSIS
17              
18             Synopsis examples use L,
19             but you may want to check out L
20             or write your own. All use the C<< A2C_Dispatch_Map >> directive,
21             but the hash structure differs between subclasses.
22              
23             =head2 EASY WAY
24              
25             This only works if you have one website on the whole server (under forked mpm)
26             because the intepreter only loads the module once and then it won't
27             load another dispatch map for other uri's.
28              
29             # vhost.conf:
30            
31             SetHandler modperl
32             A2C_Dispatch_Map /path/to/yaml/syck/dispatch/hash/file.yaml
33             PerlInitHandler Apache2::Controller::Dispatch::Simple
34            
35              
36             =head2 NORMAL WAY
37              
38             The normal way supports many separate dispatch maps on a server,
39             but each application must subclass a dispatch class, even if it
40             has no methods.
41              
42             # vhost.conf:
43             PerlLoadModule MyApp::Dispatch;
44            
45             SetHandler modperl
46             A2C_Dispatch_Map /etc/myapp/dispatch.yaml
47             PerlInitHandler MyApp::Dispatch
48            
49              
50             # /etc/myapp/dispatch.yaml:
51             foo: MyApp::Controller::Foo
52             bar: MyApp::Controller::Bar
53             'foo/bar': MyApp::Controller::Foo::Bar
54             biz: MyApp::C::Biz
55             'biz/baz': MyApp::Controller::Biz::Baz
56              
57             # lib/MyApp/Dispatch.pm:
58             package MyApp::Dispatch;
59             use base qw( Apache2::Controller::Dispatch::Simple );
60             1;
61              
62             =head2 HARD WAY
63              
64             # vhost.conf:
65             PerlModule MyApp::Dispatch
66              
67            
68             SetHandler modperl
69             PerlInitHandler MyApp::Dispatch
70            
71              
72             # lib/MyApp/Dispatch.pm:
73              
74             package MyApp::Dispatch;
75              
76             use strict;
77             use warnings FATAL => 'all';
78              
79             use base qw( Apache2::Controller::Dispatch::Simple );
80              
81             # return a hash reference from dispatch_map()
82             sub dispatch_map { return {
83             foo => 'MyApp::C::Foo',
84             bar => 'MyApp::C::Bar',
85             'foo/bar' => 'MyApp::C::Foo::Bar',
86             biz => 'MyApp::C::Biz',
87             'biz/baz' => 'MyApp::C::Biz::Baz',
88             } }
89              
90             # or use directive A2C_Dispatch_Map to refer to a YAML file.
91              
92             1;
93            
94             =head1 DESCRIPTION
95              
96             C forms the base for the
97             PerlInitHandler module to dispatch incoming requests to
98             libraries based on their URL.
99              
100             You don't use this module. You use one of its subclasses
101             as a base for your dispatch module.
102              
103             =head1 WHY A MAP?
104              
105             Natively, this does not try to figure out the appropriate
106             module using any complex magic. Instead, you spell out the
107             uris under the handler location and what controller
108             modules you want to handle paths under that URL, using a
109             directive. (L)
110              
111             The trouble with automatic controller module detectors is
112             that parsing the URI and doing C<< eval "use lib $blah" >>
113             up through the URI path is that is computationally expensive.
114              
115             Maintaining a URI map file is not that difficult and also is
116             convenient because you can move libraries around, point different
117             URI's to the same controller library, etc. For example to bring
118             part of your site off-line and see 'under construction', create
119             a controller to print the right message, change all the uri's
120             in the map and bump the server.
121              
122             (Can I trap a signal so it
123             clears and reloads map files if Apache2 is HUP'd? That would be cool.
124             Or a timeout that would cause children to reload the file.)
125              
126             Different dispatch types use different structure in the
127             map, but it is conceptually the same. The structure is
128             loaded into memory and then the uri can be parsed very
129             quickly to locate the correct controller.
130              
131             =head1 SUBCLASSES
132              
133             Subclasses of this module implement C<< find_controller() >>
134             in different ways, usually interpreting the URI from a
135             hash reference returned by C<< dispatch_map() >> in your subclass.
136             Or, if you provide the directive C<< A2C_Dispatch_Map >> to specify
137             a map file, this module will load it with L.
138              
139             See L and
140             L for other
141             dispatch possibilities.
142              
143             Any implementation of find_controller() should throw an
144             L with status C<< Apache2::Const::NOT_FOUND >>
145             in the
146             event that the detected method selected does not appear in the list of
147             C<< allowed_methods() >> in the controller module. ex:
148              
149             a2cx status => Apache2::Const::NOT_FOUND;
150              
151             See L. This is
152             internal stuff mostly, you don't have to implement your own
153             type of dispatch mechanism unless you are a nut like me.
154              
155             Successful run of find_controller() should result in four items of
156             data being set in request->pnotes->{a2c}:
157              
158             =over 4
159              
160             =item pnotes->{a2c}{relative_uri} = matching part of uri relative to location
161              
162             This is the uri relative to the location. For example,
163             if the dispatch module is the init handler in a C<< >>
164             config block, then for /subdir/foo/bar/biz/zip in this example code,
165             relative_uri should be 'foo/bar' because this is the key of %dispatch_map
166             that was matched. /subdir/foo/bar is the 'virtual directory.'
167              
168             If there is no relative uri, for example if the uri requested was /subdir
169             and this is the same as the location, then
170             C< pnotes->{a2c}{relative_uri} > would be set to
171             the empty string.
172              
173             =item pnotes->{a2c}{controller} = selected package name
174              
175             This should be the name (string) of an Apache2::Controller subclass selected
176             for dispatch.
177              
178             =item pnotes->{a2c}{method} = method name in controller to process the uri
179              
180             This is the name of the method of the controller to use for this request.
181              
182             =item pnotes->{a2c}{path_args} = [ remaining path_info ]
183              
184             The remaining 'virtual directory' arguments of the uri.
185             In the example above for pnotes->{a2c}{relative_uri}, this is [ 'biz', 'zip' ].
186              
187             =back
188              
189             @path_args is the array of remaining elements. For example if your
190             dispatch map contains the URI 'foo', and the incoming URI was '/foo/bar/baz',
191             then $r->pnotes->{a2c}{path_args} should be ['bar', 'baz'] before returning.
192              
193             =cut
194              
195 2     2   375 use strict;
  2         3  
  2         67  
196 2     2   11 use warnings FATAL => 'all';
  2         8  
  2         80  
197 2     2   17 use English '-no_match_vars';
  2         4  
  2         11  
198              
199 2         721 use base qw(
200             Apache2::Controller::NonResponseBase
201             Apache2::Controller::Methods
202 2     2   1142 );
  2         2  
203              
204             use Log::Log4perl qw(:easy);
205             use Readonly;
206              
207             use YAML::Syck;
208              
209             use Apache2::RequestRec ();
210             use Apache2::Connection ();
211             use Apache2::RequestUtil ();
212             use Apache2::Const -compile => qw( :common :http :methods );
213              
214             use Apache2::Controller::X;
215             use Apache2::Controller::Const qw( @RANDCHARS $NOT_GOOD_CHARS );
216             use Apache2::Controller::Funk qw( log_bad_request_reason );
217              
218             =head1 METHODS
219              
220             =head2 $handler->process()
221              
222             process() is the main guts of Apache2::Controller::Dispatch logic.
223             It calls $self->find_controller(), which is implemented in another
224             base class. (See L.) If that
225             works, then it creates an Apache2::Request object from $r, which will
226             supposedly parse the query string once for all further handlers that
227             create Apache2::Request objects.
228              
229             =cut
230              
231             sub process {
232             my ($self) = @_;
233              
234             my $r = $self->{r};
235             my $class = $self->{class};
236              
237             my $pnotes = $r->pnotes;
238              
239             # find the controller module and method to dispatch the URI
240             $self->find_controller();
241             my $controller = $self->{controller} = $pnotes->{a2c}{controller};
242             DEBUG "found controller '$controller'";
243              
244             # save the dispatch class name in notes in case we have to
245             # re-dispatch somewhere along the line if the uri changes
246             # (this is done by Apache2::Controller::Auth::OpenID, for instance)
247             $pnotes->{a2c}{dispatch_class} = $class;
248              
249             # set the handler for that class
250             # - this has to be the last thing it does in case an exception is thrown
251              
252             DEBUG "setting PerlResponseHandler '$controller'";
253             $r->set_handlers(PerlResponseHandler => [ "$controller" ]);
254             # "" == lame but true, must stringify lib name because
255             # the value is some kind of blessed scalar reference or something
256              
257             DEBUG sub { "Done with process() for uri ".$r->uri };
258            
259             return Apache2::Const::OK;
260             }
261              
262             =head2 dispatch_map
263              
264             The base class method relies on having directive C<< A2C_Dispatch_Map >>.
265             This loads a L file at server startup for every instance
266             of the directive. This is your best bet if you want to use a file,
267             because the file will be loaded only once, instead of every time a
268             mod_perl child process spawns.
269              
270             If you want to return a hash yourself, overload this in a
271             dispatch subclass.
272              
273             =cut
274              
275             sub dispatch_map {
276             my ($self) = @_;
277             return $self->get_directive('A2C_Dispatch_Map')
278             || a2cx "No directive A2C_Dispatch_Map";
279             }
280              
281             =head2 get_dispatch_map
282              
283             Get the cached C<< \%dispatch_map >> of the dispatch handler object's class.
284             Caches references here in parent package space and checks with C<< exists >>.
285              
286             In your dispatch subclass, you define C<< dispatch_map() >> which
287             returns a hash reference of the dispatch map.
288              
289             =cut
290              
291             my %dispatch_maps = ( );
292             sub get_dispatch_map {
293             my ($self) = @_;
294             my $class = $self->{class};
295             return $dispatch_maps{$class} if exists $dispatch_maps{$class};
296              
297             my $dispatch_map = $self->dispatch_map();
298              
299             a2cx "No dispatch_map() in $class" if !$dispatch_map;
300              
301             my $ref = ref $dispatch_map;
302             a2cx "Bad dispatch_map() in $class" if !defined $ref || $ref ne 'HASH';
303              
304             $dispatch_maps{$class} = $dispatch_map;
305              
306             DEBUG sub{"dispatch_maps:".Dump(\%dispatch_maps)};
307              
308             return $dispatch_map;
309             }
310              
311              
312             1;
313              
314             =head1 EXAMPLE
315              
316             # configuration for :
317             # PerlInitHandler MyApp::Dispatch
318              
319             package MyApp::Dispatch;
320             use base qw(
321             Apache2::Controller::Dispatch
322             Apache2::Controller::Dispatch::Simple
323             );
324              
325             my @LIMIT_HTTP_METHODS = qw( GET );
326              
327             sub dispatch_map { { # return a hash reference
328             foo => 'MyApp::C::Foo',
329             bar => 'MyApp::C::Bar',
330             biz => 'MyApp::C::Biz',
331             } }
332              
333             1;
334              
335             =head1 SEE ALSO
336              
337             L
338              
339             L
340              
341             L
342              
343             =head1 AUTHOR
344              
345             Mark Hedges, C<< >>
346              
347             =head1 COPYRIGHT & LICENSE
348              
349             Copyright 2008-2010 Mark Hedges, all rights reserved.
350              
351             This program is free software; you can redistribute it and/or modify it
352             under the same terms as Perl itself.
353              
354             This software is provided as-is, with no warranty
355             and no guarantee of fitness
356             for any particular purpose.
357              
358             =cut
359