NAME
Apache2::Controller - fast MVC-style Apache2 handler apps
VERSION
Version 1.000.110
INSTALLATION PRE-REQUISITES
You need mod_perl2, Apache::Test and version installed to build this distribution with CPAN. Otherwise the Makefile.PL will not run to tell you that prerequisites failed. This is a drawback of using Apache::Test.
SYNOPSIS
Your application IS the controller. A2C gets all the abstractions out from between your controller logic and the Apache2 methods to control input/output, status etc. You control Apache2 directly, or use a rendering base like Apache2::Controller::Render::Template which gives a method to render using Template Toolkit.
For Apache2 config file setup see Apache2::Controller::Dispatch, which sets a PerlResponseHandler of Apache::Controller, which then creates your controller object and calls the chosen method for the uri.
package MyApp::C::Foo;
use strict;
use warnings FATAL => 'all';
# base Apache2::Request is optional, it would limit your
# choice of method names. request object is in $self->{r}
# if you do not choose to use it to get access to the
# Apache2 methods via $self.
use base qw(
Apache2::Controller
Apache2::Request
);
use Apache2::Const -compile => qw( :http );
sub allowed_methods {qw( default bar baz )}
# suppose '/foo' is the uri path dispatched to this controller
# and your dispatch uses Apache2::Controller::Dispatch::Simple
# http://myapp.xyz/foo/
sub default {
my ($self) = @_;
$self->content_type('text/plain');
$self->print("Hello, world!\n");
return Apache2::Const::HTTP_OK;
}
# http://myapp.xyz/foo/bar/biz/schnozz
sub bar {
my ($self, @path_args) = @_;
# @path_args is:
# qw( biz schnozz )
# @{ $self->{path_args} }
# @{ $self->pnotes->{a2c}{path_args} }
$self->content_type('text/html');
$self->print(q{ <p>"WE ARE ALL KOSH"</p> });
return Apache2::Const::HTTP_OK;
}
# http://myapp.xyz/foo/baz
sub baz {
my ($self) = @_;
return Apache2::Const::HTTP_BAD_REQUEST
if $self->param('goo'); # inherits Apache2::Request
return Apache2::Const::HTTP_FORBIDDEN
if $self->param('boz') ne 'noz';
$self->content_type('text/plain'); # inherits Apache2::RequestRec
$self->sendfile('/etc/passwd'); # inherits Apache2::RequestIO
return Apache2::Const::HTTP_OK;
}
1;
You could implement a pretty nice REST interface, or any other kind of HTTP-based API, by returning the appropriate HTTP status codes. See "status" in Apache2::Controller::Refcard for a list.
See Apache2::Controller::Render::Template for an additional base for your controller class to render HTML with Template Toolkit, auto-selecting a template from the include path based on the request URI.
DESCRIPTION
Apache2::Controller is a lightweight controller framework for object-oriented applications designed to run only under mod_perl children in high-performance Apache2 handler modules. It features URL dispatch with flexible configuration, auth plugins, a cookie tracker for Apache::Session, liberty for any storage models that work under mod_perl, rendering using Template Toolkit or direct printing with Apache or whatever you want, and base inheritance configuration allowing you to construct your applications as you need, without trying to be all things to all people or assimilate the world. It is intended as a framework for new applications specialized as Apache2 handlers, not as a means to absorb existing applications or to create portable code.
Apache2::Controller instantiates the Apache2::Request object and puts it in $self->{r}
. If you want access to the methods directly via $self
, simply use Apache2::Request as a base and it will auto-delegate all the methods. See "SUBCLASSING Apache2::Request" in Apache2::Request.
Apache2::Controller::Upload early in your use base
list, which will add the methods from Apache2::Upload when the Apache2::Request object gets created. Apache2::Controller::Uploads is a second base module for controller modules to inherit from to allow file uploads and provide various handy file conversion routines, if you have the appropriate binaries installed.
For using other Apache2::Controller extension methods, use another base class like Apache2::Controller::Render::Template, provides an easy way to use Template Toolkit by default to render pages, selecting templates from a directory structure that corresponds to your controller URI's.
Individual controller methods can specify plain text or other content types and print directly through inherited Apache2::RequestIO methods, if you use Apache2::Request as a second base and suck its methods in through that module's auto-magic. See "SUBCLASSING Apache2::Request" in Apache2::Request.
Instead of abstracting Rube Goldberg devices around the Apache2 mod_perl methods, it stays out of your way and lets you use any and all of them directly through $self
as you see fit, if you use Apache2::Request as a second base. But you don't have to do that, if you don't want potential namespace conflicts with your uri's. For example, if you do use Apache2::Request as a base, you couldn't have a uri 'params' or 'connection', for example, if you want to be able to use those Apache2 family methods. The Apache2::Request object is always in $self->{r}
either way.
Use Apache2::Controller::Dispatch from your Apache2 config file to send various URI requests to your page view modules. See the CONFIGURATION section below. This features a standard mechanism for uri dispatch in Apache2::Controller::Dispatch::Simple that does not try to figure out what modules are available, but simply requires you to provide a hash that maps from uri paths to controller modules. Or, dispatch plugins can be created to implement the dispatcher's find_controller() method in some other way, like with a TRIE for big sites or using other algorithms, even dynamic ones based on context from the request.
Apache2::Controller is the base module for each controller module. Depending on your dispatch mechanism, controller modules usually contain a list of the method names which are allowed as uri paths under the controller.
DISPATCH OF URI TO CONTROLLER
You do not put Apache2::Controller or your subclass into the Apache2 server configuration. Instead you make a subclass of Apache2::Controller::Dispatch and use that as a PerlInitHandler. It will map a URI to an appropriate Apache2::Controller subclass object and method and will use $r->push_handlers()
if successful to push Apache2::Controller onto the modperl response handler stack, which then creates the right handler object of your subclass and sends the request to the right method, handling errors in a nice way.
See Apache2::Controller::Dispatch for more information and different types of URI dispatching. Some simple types are bundled which depend on the allowed_methods()
subroutine in your controller, but that isn't a required feature - you can also implement your own dispatch subclass which does things your way, moves allowed uris around depending on context in the request, or whatever.
OTHER REQUEST PHASE HANDLERS
Configure other handlers in your config file to set things up before your Apache2::Controller runs.
Most of these handlers use Apache2::Controller::NonResponseBase as a base for the object, which usually does not need to instantiate the Apache2::Request object, because they usually run before the response phase, so you usually don't want to parse and cache the body if you want to use input filters. If your subclass methods of non-response Apache2::Controller components need access to the Apache2::RequestRec object $r
, it is always in $self->{r}
.
Some other request phase handlers register later-stage handlers, for example to save the session or rollback uncommitted database transactions with PerlLogHandler
's after the connection output is complete.
The controller handler returns your set HTTP status code or OK (0) to Apache. In general you return the status code that you want to set, or return OK. Or you can set it with $r->status()
and return OK. You can't return DONE or DECLINED. (If you find you need to do that for some reason please contact me.)
See ":http" in Apache2::Const and Apache2::Controller::Refcard. You can also set status_line()
or throw Apache2::Controller::X exceptions to be processed by an error template, if you're using some form of template rendering - see the section on errors below.
Add handlers in your config file with your own modules which use base
to inherit from these classes as you need them:
PerlHeaderParserHandler Apache2::Controller::Session
$r->pnotes->{a2c}{session}
automatically loaded from and stored to an Apache::Session tied hash. Pushes a PerlLogHandler to save the session after the main controller returns OK.
See Apache2::Controller::Session and Apache2::Controller::Session::Cookie.
PerlAuthenHandler Apache2::Controller::Authen::OpenID
Implements OpenID logins and redirects to your specified login controller by changing the dispatch selection on the fly.
See Apache2::Controller::Authen::OpenID.
As for Access and Authz phases of AAA, you should probably roll your own. This framework isn't going to dictate the means of your data storage or how you organize your users. See the mod_perl manual.
Apache2::Controller response phase handler
Apache2::Controller is set as the PerlResponseHandler if the dispatch class finds a valid module and method for the request.
Subclass Apache2::Request
Most of the time you will want to use Apache2::Request as a second base. If you do this, then your controller inherits the Apache2::RequestRec methods with (some) modperl2 request extension libraries loaded during construction, or you can use others in the package namespace and automatically get the methods.
This way, you can call $self->$methodname
for any of the methods associated with Apache2::Request, Apache2::RequestRec and some of their friends. Watch the log for warnings about redefined subroutines, or use warnings FATAL => 'all'
to keep yourself on the right track.
To use a simplified example:
package MyApp::C::SomeURIController;
use base qw(
Apache2::Controller
Apache2::Request
);
my %pats = (
shipto => qr{ \A (.*?) \z }mxs,
addr => qr{ \A (.*?) \z }mxs,
zip => qr{ \A (\d{5}) \z }mxs,
);
sub set_shipping_address {
my ($self) = @_;
# $self->param() is Apache::Request param():
my ($shipto, $addr, $zip)
= map {
$self->param($_) =~ $pats{$_};
$1 || return Apache2::Const::SERVER_ERROR;
} qw( shipto addr zip );
$self->content_type('text/plain');
$self->print('Your package is on its way.');
return Apache2::Const::HTTP_OK
}
At any rate, your Apache2::Controller child object normally subclasses itself into Apache2::Request which magically delegates all those methods to the internal hash value $self->{r}
, which is the actual Apache2::Request object. See "SUBCLASSING Apache2::Request" in Apache2::Request about those gory details.
Whether you call $self->$apache2_request_method
or $self->{r}->$apache2_request_method
matters not, you still ask the same object, so you might as well use $self->$method
to make it look clean.
But, if you don't want to pollute your namespace of potential URI handlers with the Apache2::Request* family method namespace, don't use Apache2::Request as a base. $self->{r}
is still an Apache2::Request object, which gets you to all the RequestRec, RequestUtil, RequestIO methods etc.
The rest of this manual assumes that you do use Apache2::Request as a base in your controller, and refers to its methods via $self
. Just keep in mind that you don't have to, but can access those methods via $self->{r}
.
RETURN VALUES
Your controller methods should use eval { }
if necessary and act accordingly, set the right things for Apache2::RequestRec
and return the right HTTP constant. See ":http" in Apache2::Const and Apache2::Controller::Refcard.
In the event of an error, if you wish, use Apache2::Controller::X and throw one with field 'status' set to a valid HTTP return code. This lets you implement nice error templates if your controller uses Apache2::Controller::Render::Template as a base. See ERRORS below.
Success in the controller method normally should just return the appropriate HTTP status code. You can return HTTP_OK (200) if that is what you mean, or it is the default status if you return OK (0).
Or, if you do $self->status( Apache2::Const::HTTP_SOMETHING )
and then just return()
or return OK (0), Apache2::Controller will not override the set status.
See Apache2::Controller::Refcard for a list of HTTP return constants and corresponding numbers and messages.
REDIRECTS, ETC.
As an example of return values, take browser redirects. There's no need for an abstraction mechanism around redirects since you have direct access to the Apache methods.
package MyApp::Controller::Somewhere;
use base qw( Apache2::Controller Apache2::Request );
use Apache2::Const -compile => qw( REDIRECT );
# maybe dispatched from http://myapp.xyz/somewhere/go_elsewhere
sub go_elsewhere {
my ($self, @path_args) = @_;
$self->err_headers_out->add(Location => 'http://foo.bar');
return Apache2::Const::REDIRECT;
}
Keep in mind that if you use other request phase processors that push a PerlLogHandler
like Apache2::Controller::DBI::Connector or Apache2::Controller::Session, those will still run, but for example the session controller won't save the session if you set or return an http status higher than the HTTP_OK family (HTTP_MULTIPLE_CHOICES (300) or higher.)
You should also not fiddle with the connection by causing Apache2 to close it prematurely, else the post-response handlers may not run or won't run synchronously before another request is received that may have depended on their behavior. (For example, you can't use a PerlCleanupHandler
to do things like that because the request has already closed, and it doesn't get processed before taking in the next request, even when running in single-process mode.)
ERRORS
If you decide to set an error status code, you can print your own content and return that status code.
If you want to use error templates, barf Apache2::Controller::X objects. These print a stack trace to the error log at the WARN level of Log::Log4perl from this module's namespace. If errors crop up from other A2C request phase handlers, try setting WARN log level for Apache2::Controller::NonResponseBase or Apache2::Controller::NonResponseRequest.
Also see Apache2::Controller::Render::Template.
You can use or subclass Apache2::Controller::X, to use a2cx()
, or you can throw your own exception objects, or just die()
, or croak()
, or set $self->status
, headers etc., possibly printing content, or return the appropriate status from your controller method.
See Apache2::Controller::X for help on throwing exceptions with HTTP status, data dumps, etc.
If your code does break, die or throw an exception, this is caught by Apache2::Controller. If your controller module implements an <error()
> method, then $handler->error()
will be called passing the $EVAL_ERROR
or exception object as the first argument.
package MyApp::C::Foo;
use YAML::Syck;
# ...
sub error {
my ($self, $X) = @_;
$self->status( Apache2::Const::HTTP_BAD_REQUEST );
$self->content_type('text/plain');
$self->print("Take this job and shove it!\n", "\n", $X, "\n");
if ($X->isa('Apache2::Controller::X')) {
# usually you wouldn't show gory details to the user...
$self->print(Dump($X->dump)) if $X->dump;
$self->print($X->trace) if $X->trace;
}
}
For instance Apache2::Controller::Render::Template implements error()
for you, which looks for the appropriately named error template as template_dir/errors/###.html.
Of course, all exceptions are sent to the error log using Log::Log4perl DEBUG() before the handler completes, and any refusal status greater or equal to 400 (HTTP_BAD_REQUEST) will be written to the access log with Apache2::Log log_reason() using the first few characters of the error.
See "ERRORS" in Apache2::Controller::Session for how to control whether or not a session is saved. Usually it is automatically saved, but not if you have an error.
error()
does not have to roll back DBI handles if you use Apache2::Controller::DBI::Connector, as this is rolled back automatically in the PerlLogHandler
phase if you don't commit the transaction.
CONTROLLER CLOSURES
Apache2::Controller's package space structure lets you take advantage of closures that access variables in your controller subclass package space, which are cached by modperl in child processes across independent web requests. Be careful with that and use Devel::Size to keep memory usage down. I have no idea how this would work under threaded mpm.
CONTENT TYPE
Your controller should set content type with $self->content_type()
to something specific if you need that. Otherwise it will let mod_perl set it to whatever it chooses when you start to print. This is usually text/html.
LOGGING
Apache2::Controller uses Log::Log4perl. See that module for information on how to set up a format file or statement. For example, in a perl startup script called at Apache2 start time, do something like:
use Log::Log4perl;
log4perl.rootLogger=DEBUG, LogFile
log4perl.appender.LogFile=Log::Log4perl::Appender::File
log4perl.appender.LogFile.filename=/var/log/mysite_error_log
log4perl.appender.LogFile.layout=PatternLayout
log4perl.appender.LogFile.layout.ConversionPattern=%M [%L]: %m%n
};
Log::Log4perl->init(\$logconf);
These settings will be cloned to every modperl child on fork.
MVC
Apache2::Controller provides the controller, mainly. Apache2::Controller::Render::Template is one example of a view that can be used as a second base with use base
in your controller module. As for the Model part of Model-View-Controller, Apache2::Controller leaves that entirely up to you and does not force you to wrap anything in an abstraction class.
The handler()
subroutine is in your base class and your controller modules will be running from memory in the mod_perl child interpreter. So, you can use package namespace effectively to store data that will persist in the mod_perl child across requests.
LOAD BALANCING
A2C does not have to load all site modules for every page handler, which could help with load-balancing highly optimized handlers for specific URI's while having a universal application installer.
Picture if you will, a programming utopia in which all engineers are respected, highly paid and content, and managers make correct decisions to rely on open-source software.
You deploy the same Apache, the same CPAN modules and your whole application package to every server, and attach a url-generating subroutine to the Template stash that puts in a different hostname when the URI is one of your load-intensive functions.
<a href="[% myurl('/easy') %]">easy</a> "http://pony.x.y/easy"
<a href="[% myurl('/hard') %]">hard</a> "http://packhorse.x.y/hard"
Web designers can be taught to use this function myurl()
, but system admins maintain the map that it loads to figure out what servers to use.
Then the Apache2 config files on those packhorse servers would pre-load only the subclassed controllers that you needed, and redirect all other uri requests to the pony servers.
FUNCTIONS
a2c_new
$handler = MyApp::C::ControllerSubclass->a2c_new( Apache2::RequestRec object )
This is called by handler() to create the Apache2::Controller object via the module chosen by your Apache2::Controller::Dispatch subclass.
We use a2c_new
instead of the conventional new
because, in case you want to suck in the Apache2::Request methods with that module's automagic, then you don't get confused about how <SUPER::
> behaves. Otherwise you get into a mess of keeping track of the order of bases so you don't call Apache2::Request->new()
by accident, which breaks everything.
subclassing a2c_new()
To set params for the Apache2::Request object, you have to subclass a2c_new()
.
package MyApp::ControllerBase;
use base qw( Apache2::Controller Apache2::Request );
sub a2c_new {
my ($class, $r) = @_;
return SUPER::new(
$class, $r,
POST_MAX => 65_535,
TEMP_DIR => '/dev/shm',
);
# $self is already blessed in the class hierarchy
}
package MyApp::Controller::SomeURI;
use base qw( MyApp::ControllerBase );
sub allowed_methods qw( uri_one uri_two );
sub uri_one { # ...
If you need to do the same stuff every time a request starts, you can override the constructor through a class hierarchy.
package MyApp::ControllerBase;
use base qw( Apache2::Controller Apache2::Request );
sub new {
my ($class, $r, @apr_override_args) = @_;
my $self = SUPER::new(
$class, $r,
POST_MAX => 65_535,
TEMP_DIR => '/dev/shm',
@apr_override_args,
);
# $self is already blessed in the class hierarchy
# do request-startup stuff common to all controller modules
return $self;
}
package MyApp::Controller::SomeURI;
use base qw( MyApp::ControllerBase );
sub allowed_methods qw( uri_one uri_two );
sub new {
my ($class, $r) = @_;
my $self = SUPER::a2c_new(
$class, $r,
);
# no need to bless, A2C blesses into the child class
# do request-startup stuff for this specific controller
return $self;
}
sub uri_one {
my ($self) = @_;
$self->content_type('image/gif');
# ...
return Apache2::Const::HTTP_OK;
}
sub uri_two { # ...
Similarly, to do something always at the end of every request, from within the dispatched PerlResponseHandler:
package MyApp::Controller::SomeURI;
use Devel::Size;
use Log::Log4perl qw(:easy);
my $MAX = 40 * 1024 * 1024;
sub DESTROY {
my ($self) = @_;
my $size = total_size($self); # whoo baby!
INFO("size of $self->{class} is bigger than $MAX!") if $size > $MAX;
return; # self is destroyed
}
See "USING INHERITANCE" below for more tips.
METHODS
Methods are also extended by other modules in the A2C family. See Apache2::Controller::Methods.
handler
# called from Apache, your subclass pushed to PerlResponseHandler
# by your A2C dispatch handler:
MyApp::Controller::Foo->handler( Apache2::RequestRec object )
The handler is pushed from an Apache2::Controller::Dispatch subclass and via your dispatched subclass of Apache2::Controller. It should not be set in the config file. It looks for the controller module name in $r->pnotes->{a2c}{controller}
and for the method name in $r->pnotes->{a2c}{method}
.
Errors are intercepted and if the handler object was created and implements an $handler->error($exception)
method then the exception will be passed as the argument.
An HTTP status code of HTTP_BAD_REQUEST or greater will cause log_reason to be called with a truncated error string and the uri for recording in the access log.
USING INHERITANCE
There is no need for a predefined sequence of start-up or clean-up routines that Apache2::Controller would have to check for in your controller module.
Instead, you use inheritance to streamline your code and share common pieces, like in "subclassing a2c_new( )" above.
If your methods need to do cleanup after finishing, for example, they should add a line to call a shared cleanup method.
package MyApp::Cleanup;
sub cleanup {
my ($self) = @_;
# ...
}
package MyApp::C::Foo;
use base qw( Apache2::Controller Apache2::Request MyApp::Cleanup );
sub allowed_methods {qw( foo bar )}
sub foo {
# ...
$self->cleanup();
return;
}
# or...
sub bar {
# ...
$self->push_handler(PerlCleanupHandler => sub {
# ...
});
return;
}
Or better yet...
package MyApp::Cleanup;
sub DESTROY {
my ($self) = @_;
# ...
}
package MyApp::C::Foo;
use base qw( Apache2::Controller MyApp::Cleanup );
sub allowed_methods {qw( foo bar )}
sub foo {
# ...
return;
}
sub bar {
# ...
return;
}
There is no need for a predefined method sequence that tries to run for each request, because Apache2 already provides a robust abstraction of the request lifecycle with many stages for which you can register handler subroutines. If you can wrap your head around it, inheritance provides many solutions to problems for which elaborate measures are commonly re-invented. For example if you wanted cleanup done the same way every time without having to remember that $self->cleanup()
line for each new method, overload the constructor as per "subclassing a2c_new( )" above and register a PerlCleanupHandler for every request instead, or use a base with a DESTROY method.
Otherwise the framework ends up doing a lot of work every time to ask, "did they implement this? did they implement that?" and that gets in your way, or you have to write those routines every time even if they don't do anything, or whatever. Bleah. Implement what you want to implement from the controller methods. The framework won't provide you with any more structure.
EXAMPLES
Browse the source package from CPAN and check out t/lib/* and t/conf/extra.conf.last.in.
RELATED MODULES
Apache2::Controller::Directives
Apache2::Controller::DBI::Connector
Apache2::Controller::Auth::OpenID
SEE ALSO
Apache2::RequestRec and friends
http://perl.apache.org/docs/2.0/user/handlers/http.html
THANKS
Many thanks to David Ihnen, Adam Prime, André Warnier and all the great people on the modperl mailing list.
Special thanks to Nobuo Danjou for Apache2::AuthenOpenID which edumacated me on how the OpenID authen module should work.
Of course, thanks to the many mod_perl and Apache authors and all the CPAN authors whose modules this depends on. Wow! This stuff is so cool!
AUTHOR
Mark Hedges, hedges +(a t)- formdata.biz
COPYRIGHT AND LICENSE
Copyright 2008-2010 Mark Hedges. CPAN: markle
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
This software is provided as-is, with no warranty and no guarantee of fitness for any particular purpose.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 993:
Non-ASCII character seen before =encoding in 'André'. Assuming UTF-8