MasonX::WebApp - Works with Mason to do processing before Mason is invoked
# Create a subclass of MasonX::WebApp package My::WebApp; use base 'MasonX::WebApp'; sub _init { # do neat stuff } # In your Apache config file <Location /> SetHandler perl-script PerlHandler My::WebApp </Location>
MasonX::WebApp works with Mason to let you do processing before Mason is ever invoked. There are a number of things that one might want to do:
MasonX::WebApp
Argument munging
You might want to make sure all incoming arguments are UTF-8 encoded. Or you might want to create some objects which Mason will see as incoming arguments. For example, a "user_id" parameter could be turned into a user object.
Handle requests without Mason
If you're not generating output for the browser other than a redirect, then there's no reason to use Mason. You can use a MasonX::WebApp subclass to handle all form submissions, for example.
This has the added benefit of making it easier to preload this code once during server startup.
Authorization checks
Why do authorization checks in Mason if a failed check just leads to a redirect or NOT FOUND return code?
To use MasonX::WebApp, you should create a MasonX::WebApp subclass. By itself, MasonX::WebApp won't do a whole lot for you, but it provides a nice framework for building on.
MasonX::WebApp, out of the box, provides the following:
Session creation
You can declare your session parameters, and MasonX::WebApp will create an Apache::Session::Wrapper object for you, available via the session() method. Alternately, you can implement your own session creation method in your subclass.
Apache::Session::Wrapper
session()
The arguments which will eventually be passed to Mason are available via the args() method. This method returns a hashref, and any changes made to this reference will affect the arguments eventually passed to Mason.
args()
"Actions"
MasonX::WebApp will call appropriate methods based on the URI. These methods are determined by removing a prefix from the URI (settable via a class method), and then using the remainder as a method name to be called on the webapp object.
Messages, errors, and "saved arguments"
If you are using sessions, the webapp object provides methods to store regular messages, error messages, and save arguments (to re-populate a form, for example) in the session. It also provides methods to retrieve these.
Convenient uri creation
The uri() method provides a nice flexible API for creating URIs.
uri()
You can set some parameters for your subclass declaratively, by calling class methods. These methods store data using Class::Data::Inheritable, so you can inherit from your subclasses and inherit these parameters.
Class::Data::Inheritable
The following class methods are offered for declaring parameters:
ActionURIPrefix
This is the prefix used to determine which, if any, "action" method should be called on the webapp object. By default, this is /submit/. So if a request comes in for /submit/login, then the login() method will be called.
login()
If you change this, your prefix must also start and with a slash (/).
ApacheHandlerParams
This should be a hash reference of options that will be passed to the MasonX::WebApp::ApacheHandler class's new() method when creating a new ApacheHandler object. You don't need to set this if you are creating the ApacheHandler from scratch in your subclass, and/or if you are providing your own mod_perl handler() subroutine/method.
MasonX::WebApp::ApacheHandler
new()
handler()
The default handler() will create a new MasonX::WebApp::ApacheHandler object on every request, using these parameters.
MasonGlobalName
The variable name to use for the webapp object in Mason components. The default handler() sets this global.
The default value for this is $WebApp.
$WebApp
SessionWrapperParams
A hash reference of parameters to be passed to the Apache::Session::Wrapper class's new() method.
You don't need to set this if you are creating your own session wrapper object.
Setting this also causes UseSession to be set to a true value.
UseSession
Set this to true if you are creating your own session wrapper object, so that MasonX::WebApp knows it can call session() internally.
Some methods throw exceptions. Exceptions classes are created using Exception::Class.
Exception::Class
The folowing methods are public, and can be called from subclasses or from elsewhere, like in Mason components.
This is the constructor method. It expects to receive two arguments:
apache_req
An Apache request object, which must be an object in the Apache class, or a subclass (like Apache::Request or Apache::Filter).
Apache
Apache::Request
Apache::Filter
args
A hash reference of arguments. If you are using the MasonX::WebApp::ApacheHandler class, you can use the return value of its request_args() method.
request_args()
The new method will do the following:
Call _set_session() if UseSession() is true.
_set_session()
UseSession()
Call _init(), if you have an _init() method defined in your subclass. The call to _init() is wrapped in an eval block. If an exception is throws, and that exception is not a MasonX::WebApp::Exception::Redirect exception, then it will be rethrown. Redirect exceptions are not rethrown.
_init()
MasonX::WebApp::Exception::Redirect
Call _handle_action().
_handle_action()
Return the newly created webapp object.
Returns the Apache request given to the new() method.
Returns a hash reference containing the arguments passed to the new() method. Since this is the same reference as is stored in the MasonX::WebApp::ApacheHandler object, any changes to this reference will be visible to Mason components.
session_wrapper
Returns the Apache::Session::Wrapper object for the webapp object.
If UseSession() is not true, calling this method throws an exception.
session
A shortcut for calling $webapp->session_wrapper->session.
$webapp->session_wrapper->session
redirect
This method can take a number of named parameters. If it is given a "uri" parameter, then it uses this URI for the redirection. Otherwise, it takes any parameters it is given and calls the uri() method with them. When it calls uri(), it sets the "xhtml" parameter to false, so you do not need to do this.
If called inside the context of a Mason request, it calls redirect() on the Mason request object.
redirect()
Otherwise it sets the value of redirected() to true, sends a redirect using the apache request object, and then throws a MasonX::WebApp::Exception::Redirect exception.
redirected()
redirected
Returns a boolean value indicating whether or not redirect() has been called on the webapp object.
uri
This creates a URI string based on the parameters it receives. It accepts the following parameters:
path
The path portion of the URI. This is the only required parameter.
query
A hash reference which will be turned into a query string. The keys of the hash reference may point to scalars, array references, or hash references. Hash reference values are treated the same way as array references.
fragment
Optional
host
Optional. By default, URIs are relative, and this is not used.
port
Optional. This is ignored unless "host" is also passed.
scheme
Defaults to "http", but since URIs are relative by default, this is ignored unless "host" is also passed.
username
password
Optional. These are both ignored unless "host" is also passed. If "password" is passed without a "username", it is ignored.
xhtml
Defaults to true. If this is true, then the returned URI will have any ampersands (&) in the query string HTML-escaped (&).
messages
Returns an array of non-error messages stored in the session. This method is destructive, as calling it removes the messages from the session.
If you are not using sessions, calling this method throws an exception.
errors
Returns an array of error messages stored in the session. This method is destructive, as calling it removes the error messages from the session.
saved_args
Returns a hash reference of arguments saved in the session. This method is not destructive. If you are saving arguments in the session, you should probably make sure that clean_session() is called at the end of every request. The default handler() sub does this.
clean_session()
clean_session
Removes any messages, error messages, and saved args stored in the session. This should be called a the end of each request in order to prevent these value leaking over into the next request.
These methods are intended to be called, and/or overridden by your subclass.
_LoadActions
If you want to define actions in other files, like My::WebApp::User, this method provides a handy way to load all of them at once. It looks for modules under your subclass's package name and loads them. So if your subclass is in the package Foo::Bar::WebApp, then it looks for modules matching Foo::Bar::WebApp::*.
My::WebApp::User
Foo::Bar::WebApp
Foo::Bar::WebApp::*
_make_session_wrapper
This method is called during object construction if UseSession is true. By default, it creates a new Apache::Session::Wrapper object with the parameters from SessionWrapperParams. You can override this method to provide your own session wrapper creation.
_handle_action
This method is called during object construction. If a redirect was done earlier in the object creation process, then it does nothing. Otherwise, it looks at the requested URI to see if it matches the ActionURIPrefix. If it does, it turns the URI into a method name by stripping off the prefix, and it calls that method on the webapp object.
You can override this to provide your own dispatching system for requests.
Note that this method should not call out to Mason. It should only be used for actions that don't need Mason.
_save_arg
Given a key and value, this method saves them in the session so that they will be available via the saved_args() method.
saved_args()
_add_message
Given a string, this method stores that string in the session so that it is available via the messages() method.
messages()
_add_error_message
Given a string, this method stores that string in the session so that it is available via the errors() method.
errors()
_handle_error
This method can be used to handle exceptions that occur during actions.
It provides a quick way to store error messages and arguments in the session, and then issue a redirect.
It takes several parameters:
error
This should be either a scalar or an object. If it is a scalar, this is assumed to be a simple error message.
If an object is given, then it first looks for a messages() method in that object. This method should return an array of scalars, each of which represents an error message.
Otherwise it looks for a method called message(), which should return a single scalar.
message()
It adds each error message to the session via the _add_error_message() method.
_add_error_message()
save_args
This is a hash reference of arguments that should be saved in the session. Each key/value pair will be saved by calling the the _save_arg() method.
_save_arg()
All other arguments are passed along to the redirect() method.
_apache_handler_object
This method is called in the default handler() method in order to create a new MasonX::WebApp::ApacheHandler object. It simply calls that class's new() method with the parameters set via ApacheHandlerParams.
In order to avoid stepping on your toes, all hash keys in the webapp object, and all keys that it creates in the session object, are of the form "__blahblah__". In other words, they always start and end with two underscores (__). This should make it easy to avoid name conflicts when subclassing this module or when using the session it provides.
The MasonX::WebApp class provides a default handler method. This is the code:
sub handler ($$) { my $class = shift; my $r = shift; my $ah = $class->_apache_handler_object; my $args = $ah->request_args($r); my $app = $class->new( $r, $args ); return Apache::Constants::REDIRECT() if $app->redirected; if ( $ah->interp->compiler->can('add_allowed_globals') && defined $class->MasonGlobalName ) { $ah->interp->compiler->add_allowed_globals( $class->MasonGlobalName ); $ah->interp->set_global( '$App' => $class->MasonGlobalName ); } my $return = eval { $ah->handle_request($r) }; my $err = $@; # We want to wipe out the variable before the request ends, # because if the $ah variable persists, then so does the interp, # which means the $app object won't be destroyed until the next # request in this process, which can hose up sessions big time. $ah->interp->set_global( '$App' => undef ); $app->clean_session if $class->UseSession; die $err if $err; return $return; }
I would recommend that instead of using this, you create your own mod_perl handler that does something similar, because this one is not very efficient, given that it creates a new MasonX::WebApp::ApacheHandler object for each request. It is provided primarily as a reference implementation, and so that others can experiment with this webapp code quickly.
In your own handler, there are several important guidelines you should follow.
First of all, your should use the MasonX::WebApp::ApacheHandler class. This is a subclass of Mason's ApacheHandler class that caches the value of request_args(). This is done so that these arguments can be passed to the MasonX::WebApp constructor and still be made available to Mason. It also makes sure that Mason's arguments are the same hash reference as is available from the args() method. This is very important if you want to do any argument munging in your subclass. Also, since mod_perl will only read POSTed data once, without this caching Mason would not see any arguments at all!
After creating a new webapp object, make sure to check the value of the redirected() method for that object. If it is true, you should return the REDIRECT constant from Apache::Constants.
REDIRECT
If you are using the message, error message, or saved arg features, you should make sure that clean_session() is called at the end of every request. This means that you need to wrap the call to the ApacheHandler's handle_request() method in an eval block.
handle_request()
If you use the set_global() method to make the webapp object available to your components, and your ApacheHandler objects persist across requests, then you need to call set_global() again after the request is handled, and this time set that global to undef. This ensures that the webapp object will be destroyed.
set_global()
A safer alternative, if you know what class your components will be compiled in, is to do this:
local $HTML::Mason::Commands::App = $app;
The use of local ensures that $app will go out of scope at the end of handler() subroutine.
local
You can, of course, do anything you want in your own handler() method. I often create an Apache::Request object with a "POST_MAX" parameter, in order to prevent a DoS from a ridiculously large POST.
I also often handle errors without dying, and instead will log them and present a more friendly page to the user. If you want to do this, keep in mind that constructing a webapp object can throw exceptions, so you may want to trap these in an eval block.
eval
If you do something cool with this code, write about it on the Mason HQ site, masonhq.com (which is a big wiki), or send a post to the Mason users list.
As of this writing, the most recent version of Class::Data::Inheritable (0.02) has a bug which causes subroutine redefined errors when MasonX::WebApp is loaded. These errors can safely be ignored.
If you like the basic idea of this code (run things before a Mason component is invoked), but you don't want to create a subclass, I encourage you to take a look at David Wheeler's MasonX::Interp::WithCallbacks module. In fact, I encourage you to take a look at it anyway, since it may be more appropriate than this one, depending on your needs.
MasonX::Interp::WithCallbacks
Bug reports and requests for help should be sent to the mason-users list. See http://www.masonhq.com/resources/mailing_lists.html for more details.
Dave Rolsky, <autarch@urth.org>
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the LICENSE file included with this module.
To install MasonX::WebApp, copy and paste the appropriate command in to your terminal.
cpanm
cpanm MasonX::WebApp
CPAN shell
perl -MCPAN -e shell install MasonX::WebApp
For more information on module installation, please visit the detailed CPAN module installation guide.