NAME
Apache2::Controller - lightweight OO framework for Apache2 handler apps
VERSION
Version 0.01
SYNOPSIS
For Apache2 config file setup see Apache2::Controller::Dispatch, which pushes a PerlResponseHandler of Apache::Controller, which then instantiates your controller object and calls the chosen method for the uri.
package MyApp::C::Foo;
use strict;
use warnings FATAL => 'all';
use base qw( Apache2::Controller );
use Apache2::Const -compile => qw( :http );
use Readonly;
Readonly our @ALLOWED_METHODS => qw( default bar baz );
# suppose '/foo' is the uri path dispatched to this controller
# and your dispatch uses L<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) = @_;
my @path_args = @{ $self->{path_args} }; # qw( biz schnozz )
$self->content_type('text/html');
$self->print(q{
<p>Sometimes you eat the bear,
and sometimes the bear eats you.</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 "http_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.
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 for OpenID, a cookie session and MySQL store, a pluggable infrastructure for your own models and views, 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, and without having to load all site modules for every page handler. 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 subclasses Apache2::Request, and pulls in methods from Apache2::RequestRec, Apache2::RequestIO, Apache2::RequestUtil, Apache2::Log, Apache2::Module.
For using other Apache2 request extension methods, use another base class like 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.
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.
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.
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.
Apache2::Controller is the base module for each controller module. Your controller modules then contain a list of the method names which are allowed as uri paths under the controller. Instead of implementing a complex scheme of subroutine attributes, you maintain a list, which also acts as your documentation in one place within the controller. This frees you to structure your controller module as you want to, with whatever other methods you choose to put in there.
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 handler stack. See Apache2::Controller::Dispatch for more information and different types of URI dispatching.
OTHER REQUEST PHASE HANDLERS
Add handlers in your config file your own modules which use base
to inherit from these classes if you need to add them to your config file:
PerlInitHandler Apache2::Controller::Session::Cookie
Provides a cookie-based session mechanism using YAML::Syck as the serializer, and requires DBI connection information. If you want to use some other session mechanism, you can plug it in as a different module.
PerlAuthenHandler Apache2::Controller::Auth::OpenID subclass
Implements OpenID logins and redirects to your specified login page.
PerlAuthzHandler Apache2::Controller::Groups subclass
These handlers for other stages will return DECLINED or DONE if necessary to prevent running your Apache2::Controller.
Apache2::Controller RESPONSE PHASE HANDLER
Apache2::Controller is pushed as the PerlResponseHandler if the dispatch class finds a valid module and method for the request. Your controller uses
SUBCLASS OF Apache2::Request
Apache2::Controller is a subclass of Apache2::Request, which inherits the Apache2::RequestRec object with most of the modperl2 request extension libraries loaded during construction. So 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.
package MyApp::C::SomeURIController;
# ...
sub set_shipping_address {
my ($self) = @_;
# $self->param() automatically calls Apache::Request param():
my ($shipto, $addr, $zip)
= map {
my $detaint = "detaint_$_";
$self->$detaint( $self->param($_) )
} qw( shipto addr zip );
# ...
}
sub detaint_shipto { # ...
At any rate, your Apache2::Controller child object subclasses itself into Apache2::Request and delegates all those methods to the internal hash value $self->{r}, which is the actual Apache2::Request object. See 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->...
to make it look clean.
RETURN VALUES
Your controller methods should use eval { } if necessary and act accordingly, set the right things for Apache2::RequestRec
and return the right Apache2::Const/:http
.
In the event of an error, if you wish, use Apache2::Controller::X and throw with field 'http_status' set to a valid HTTP return code. See ERRORS below.
Success in the controller method normally should just return the appropriate HTTP status code.
Or, if you do $self->http_status( Apache2::Const::HTTP_SOMETHING )
and then just return, Apache2::Controller will not override the set status.
See Apache2::Controller::Refcard for a list of HTTP return constants and corresponding numbers and messages.
ERRORS
Apache2::Controller::X is the Exception::Class hierarchy used to kick up errors and redirects. You can use or subclass this, or you can throw your own exceptions with Exception::Class or another exception object, or just die()
, but you either die with an Apache2::Controller::X subclass with an http_status field or should not die and return an HTTP status code instead.
See Apache2::Controller::X for help on throwing exceptions and external redirects.
If your code does break, die or throw an exception, this is caught by Apache2::Controller. If your controller module implements an error()
method, for instance by use of the base Apache2::Controller::Render::Template which looks in template_dir/errors/ for the appropriately named error template, then $handler-
error()> will be called passing the $EVAL_ERROR or exception as the first argument.
package MyApp::C::Foo;
use YAML::Syck;
# ...
sub error {
my ($self, $X) = @_;
$self->http_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;
}
}
Of course, all exceptions are sent to the error log using Log::Log4perl DEBUG() before the handler completes, and any refusal http_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.
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.
CONTENT TYPE
Your controller should set content type with $self->content_type($ct)
to something specific if you need that. Otherwise it will let mod_perl set it to whatever it chooses when you start to print. I guess this is usually text/html.
REDIRECTS
Use or subclass Apache2::Controller::X, and then throw an Apache2::Controller::X::Redirect or your Exception::Class object which isa Apache2::Controller::X::Redirect.
Apache2::Controller::X::Redirect->throw("http://perl.apache.org");
Apache2::Controller::handler() will trap the exception and send redirects on their way. See Apache2::Controller::X.
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; # 'Screen' is STDERR, which is error log
my $logconf = q{
log4perl.rootLogger=DEBUG, Screen
log4perl.appender.Screen=Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout=PatternLayout
log4perl.appender.Screen.layout.ConversionPattern=%M [%L]: %m%n
};
Log::Log4perl->init(\$logconf);
These settings will be cloned to every modperl child on fork.
WARNINGS AND CAVEATS
Handle errors appropriately
If you return an error code of 400 HTTP_BAD_REQUEST or higher, Apache2::Controller will return DONE instead of OK, so Apache2 will not process any further handlers in the stack but will end the request right here.
So, it's up to you in your controllers to use eval { }
appropriately and deal with errors, for instance, if your database transaction goes awry, it's up to you to roll back a transaction in the controller module. Don't push a PerlCleanupHandler to do that, though it's tempting to do so, because if you return an error code from the controller method, the PerlCleanupHandler will not run.
FUNCTIONS
Apache2::Controller::handler( Apache2::RequestRec object )
The handler is pushed from an Apache2::Controller::Dispatch subclass and should not be set in the config file. It looks for the controller module name in $r->notes->{controller}
and for the method name in $r->notes->{method}
.
It runs start_request() if implemented, then runs the selected method, then runs end_request() if implemented, setting the http_status to any positive return value returned by any of those methods. (The last positive value returned will be the final http_status.)
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.
RUN_ALL mode
In the odd chance you plan to push further PerlResponseHandlers from your controller or server config, maybe to print debugging data at the end in HTML comments for instance, then you need to set a boolean directive (temporarily an environment variable) called A2CRunAllResponseHandlers
. Then Apache2::Controller
will check Apache's stack of PerlResponseHandlers for the Response phase of the request lifecycle, to see whether you have pushed more response handlers to the stack, and if so will run them. Otherwise, the Response phase will stay in default RUN_FIRST mode and no pushed PerlResponseHandlers will be executed.
MyApp::C::ControllerSubclass->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.
If your controller defines local TEMP_DIR or POST_MAX or the Apache2::Controller::Directives A2C_TEMP_DIR or A2C_POST_MAX, these will be applied as settings during construction of the Apache2::Request object from the Apache2::RequestRec object.
subclassing new( )
If you need to do the same stuff every time a request starts, you can override the constructor.
package MyApp::InitController;
sub new {
my $self = SUPER::new(@_);
$self->push_handlers(PerlCleanupHandler => sub {
my ($r) = @_;
my $dbh = $r->pnotes->{dbh};
$dbh->rollback() unless $r->notes->{commit_success};
});
return $self;
}
package MyApp::C::Foo;
use base qw( Apache2::Controller MyApp::InitController );
# ...
See "USING INHERITANCE" below for more tips.
METHODS
$self->fields( @list_of_field_names )
Returns hash reference of query params from a list of field names. Uses the common convention of returning more than one value if present in the query, it returns an array ref, or it returns a scalar if only one value. If no field names passed, returns a hash ref of all query params.
$self->server_url( )
This returns 'protocol://' concatenated with $r->get_server_name, and if $r->get_server_port is anything other than the protocol's standard port number, it will concat ':portnum'.
$self->root_directory( )
$r->document_root with everything after last / cut out. Why? What use is this?
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 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 MyApp::Cleanup );
my @ALLOWED_METHODS = qw( foo bar );
sub foo {
# ...
$self->cleanup();
return;
}
sub bar {
# ...
$self->cleanup();
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 new( )" above and register a PerlCleanupHandler for every request instead.
SEE ALSO
Apache2::Controller::Auth::OpenID
Apache2::RequestRec and friends
AUTHOR
Mark Hedges, hedges +(a t)- scriptdolphin.org
COPYRIGHT AND LICENSE
Copyright 2008 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 stuff is for somewhere else, needs to be split from Dolph stuff
sub save_session { my ($self) = @_; my ($dbh, $sid, $session) = @{$self}{qw( dbh sid session )}; $dbh->do( q{ UPDATE session SET yaml = ? WHERE sid = ? }, undef, Dump($session), $sid ); return; }