(You are Anonymous)

Understanding the application flow of CGI::Application

By Steve Comrie, steve@unobserved.org, August 8th, 2003

Purpose of this article

The purpose of this article is to hopefully shed a little more light on the exact order that CGI::Application executes its internal & overloadable methods in. Hopefully it will also serve as a guide on better utilizing, through clearer understanding, the methods that get executed everything your instance script is executed.

I originally set out to create this article due to all the new pre / post function hooks that have been added to CGI::App since I started using it. I don't spend a lot of time on the specifics of calling each function that I talk about in this article, I assume that you already have a good working knowledge of all available overloadable CGI::App functions. If not, please read the CGI::Application Manual

This article is a work-in-progress and any feedback, comments, requests for additional samples or further explanation are greatly appreciated. Thanks to Mark Stosberg for his comments on the original draft of this article, and suggestions for other topics to touch on.

Determining run-mode

TO-DO : How the main instance run mode is determined (including start_mode, mode_param and AUTOLOAD)

Standard method call execution order

There are two main groups of methods that are executed over the life span of a CGI::Application object.

The first group is called when you create a new object from your instance script. This group handles initialization of the object (database connections, session handling, configuration file loading, etc), tell CGI::App where in the query string to look for the run-mode parameter and allows the programmer to define available run-modes.

The second group is called when you tell the object to run. This group of methods handles the actual operations required by a particular run mode and is responsible for returning some sort of output to the browser.

Group 1 - Initialization

When a New CGI::Application object is created (in your instance script)

Code example
---------------------------------------------------------------------------
my $webapp = WebApp->new();

CGI::Application methods called
---------------------------------------------------------------------------
a. cgiapp_init()
b. setup()

If your code is error free, when creating a new object, both cgiapp_init() and setup() will ALWAYS execute, whether they are the default methods provided by CGI::Application, or you have overloaded them in a super class, etc. The only way to stop one of these methods from executing is to die() or exit() from your code.

The query() method is available to you from within both cgiapp_init() & setup(), but the run mode that will be executed is not known by CGI::Application until AFTER setup() has executed (more exactly until run() is executed). Therefore you can not change the run-mode that will be executed at this stage. That's what cgiapp_prerun() is for.

Furthermore, you can not ask CGI::Application to accurately tell you what run mode will be executed via get_current_runmode(). It's a good idea at this point to leave all run mode specific access tests until we know for sure which run mode is being accessed. The only tests you could possibly want to conduct at this point in a CGI::Applications life is an all-or-nothing test to determine if certain known conditions pass tests.

IF you require that an application can only be run if certain conditions are met, i.e. a cookie is present, or a particular $ENV{REMOTE_HOST} is matched, you can execute this all-or-nothing test in cgiapp_init() and manually halt execution of the script if the test fails.

The following code is a bit of a hack, and is only suggested if you know exactly what you're doing and why it's neccesary to do it in cgiapp_init() instead of cgiapp_prerun(). This code assumes that a certain condition hasn't been met and you want the application to redirect the user somewhere else and immediately halt execution of the appliction. You would require code to test this condition in cgiapp_init() and if the test failed you get the code to execute the following function.

# redirect the application to a particular url and halt execution of the
# CGI::Application immediately
sub redirect_output_now
{
   # grab arguments
   my $self   = shift;
   my $url      = shift;

   # set CGI::App header type
   $self->header_type('redirect');

   # set CGI::App header properties
   $self->header_props( -location   => $url );

   # run teardown - we're going to be exiting the script outside of
   # CGI::Applications regular execution order, so we may want to call
   # teardown()
   $self->teardown();

   # send headers and exit - _send_headers() is the private CGI::App
   # method that output proper MIME headers
   print $self->_send_headers();

   # we need to tell the script to exit NOW, otherwise after this function
   # has executed CGI::Application will continue to run and the eventual
   # result will be two sets of headers and the output from a run mode we
   # didn't want to execute
   exit;
}

There is one thing to note with performing this style of all-or-nothing test before we know what the run mode is. If your application requires a cookie based authentication utilizing login screen & validation run-modes they will (or at least for the purposes of simplicity) need to be handled by an entirely seperate CGI::Application module. The reason for this is simple.

If you require a condition to be met before the run-mode is known then that condition must ALWAYS be met or the script will take some action. That means if you had your login screen & validation methods in the same package as the rest of your run modes a user that hasn't met the condition will never be able to see the login screen or attempt validation and essentially never be able to meet the requirements.

If you have your login screen & validation run modes in a seperate package then you can eliminate an all-or-nothing test from that module all together; any user will always be able to access those run modes. Then your main application can make use of an all-or-nothing test and push the user to the instance script for the login screen & validation module if they fail the test.

Once the user has been validated, the login & validation module can then redirect the user to the instance script for your main application where they will (code-willing) pass the all-or-nothing test and be allowed access to the entire application.

Group 2 - Action

When you call run() on a CGI::Application object (from your instance script)

Code example
---------------------------------------------------------------------------
$webapp->run();

CGI::Application methods called
---------------------------------------------------------------------------
a. cgiapp_prerun()
b. the main instance runmode
c. cgiapp_postrun()
d. teardown()

Like Group 1, once you tell your CGI::Application object to run, each of these functions will be executed unless you tell your script to die() or exit(). Do not assume that because you perform a test and 'return' some html code in cgiapp_prerun() that CGI::Application will skip the main instance run mode and jump directly too sending code to the browser.

CGI::Application does not care what you return from cgiapp_prerun(), it does not store the output of that function. The purpose of cgiapp_prerun() is very straight forward - it is the first (and only) time you can write code before the main instance run mode is executed when you KNOW EXACTLY what that run mode is.

If you create a test on that run mode and decide that you don't want CGI::Application to go through with running it you have 2 options. One is to use the same type of redirect_output_now() method given in the example above, and the second is to make use of the prerun_mode() method.

prerun_mode() allows you to change which run mode CGI::Application is about to execute, and the ONLY place you can call it is from inside cgiapp_prerun(). The following code is an sample cgiapp_prerun() method & single run mode that will redirect a user to perl.com if CGI::Application is about to execute the run mode 'mode1' and the user's IP # isn't '127.0.0.1'.

# test the run mode about to be executed
sub cgiapp_prerun
{
   my $self   = shift;
   my $runmode   = shift;

   # if executing 'mode1' user remote IP must be 127.0.0.1
   if( $runmode eq 'mode1' && $ENV{REMOTE_HOST} ne '127.0.0.1' )
   {
      $self->prerun_mode('redirect_to_perl');
   }
}

# redirect the user to perl.com
sub redirect_to_perl
{
   my $self = shift;

   # set CGI::app header props & type
   $self->header_props( -uri => 'http://www.perl.com/' );
   $self->header_type( 'redirect' );
}

Tips, Tricks, Challenges, etc.

Calling cgiapp_init() in both a Super Class & Application Module

If you are making good use of cgiapp_init() in a Super Class that all your application modules inherit from, there may come a time when you want a particular module to perform additional unique tasks during initialization. Or perhaps you're using more than one level of Super Classes. Putting a cgiapp_init() function in an Application module would overload the one present in the Super Class, but you don't want to perform the new tasks all the time in every other Application module.

Solution 1 - Use setup()

Each application module usually has its own setup() method. You could use this space to perform more tasks than simply listing the run modes. This is the most straight forward solution, but will fail if you're using more than one level of Super Class and want the tasks performed at a Sub-Super Class level as opposed to just an Application module level.

Solution 2 - Create you own overloadable function

In your Super Class module create a function that looks like:

sub app_module_init
{
   my $self = shift;
   # Nothing to init, yet!
}

Then from the cgiapp_init() method in your Super Class module, make a call to this new app_module_init() function. Since it's currently empty it won't do anything, however, if you create an app_module_init() function within your Application module it will overload the one from your Super Class. It will now get called whenever cgiapp_init is called in the Super Class. If a particular Application module doesn't have an app_module_init() method in it, no harm done, the default empty one is called in its place.

Solution 3 - use SUPER::

This is a less elegant solution to the previous two, and may not be the best fit in all cases (or at all). If your Super Class and your Application module both have cgiapp_init() run modes and you want both to be executed, you can add the following line to the top of the cgiapp_init() function in your Application module.

$self->SUPER::cgiapp_init();

This will tell perl to execute the cgiapp_init() function from the Super Class and then continue executing any additional code in the Application modules cgiapp_init() function.

Solution 4 - use callback

the following are taken out from Ceek's comment from mailing list. url here http://www.mail-archive.com/cgiapp@lists.erlbaum.net/msg05218.html

using Michael's suggestion and set up a callback (which will always run regardless of what you do in the subclass). That is safer, since you don't have to remember to add that SUPER call in the subclass everytime.

__PACKAGE__->add_callback(init => \&_my_init);

Just put that at the top of your base class somewhere (not inside a subroutine), and make sure you are using at least CGI::Application 4.0, which is when the callback code was added. That will execute the _my_init method for every runmode request during the 'init' stage. The only problem you may run into, is that the init callbacks are called immediately after the cgiapp_init method is called. So your subclass can not do anything in cgiapp_init that depends on something that happens in your init callback, since it will not have been executed yet.

Making the best use of cgiapp_postrun

TO-DO: method purpose, differences between postrun & teardown. plus examples of use (Sam Tregar's use of HTML::Lint, etc)


Here's what I do with my cgiapp_postrun:

sub cgiapp_postrun {
    my $self = shift;
    my $output_ref = shift; 

    $self->menu_template->param(LOOP_MENU => $self->menu,
                                MENU_NAME => $self->menu_name);

    my $menu = $self->menu_template->output;

    my $page_title = defined($self->page_content->{page_title}) ? 
      $self->page_content->{page_title}: $self->page_content->{description};

    $self->base_template->param(USER_TITLE1  => $self->site_content->{title1}{value},
                                USER_TITLE2  => $self->site_content->{title2}{value},
                                USER_TITLE3  => $self->site_content->{title3}{value},
                                CONT_HEADER  => $page_title,
                                CONT_ALIGN   => $self->page_content->{page_align},
                                MENU_TEMPEST => $menu,
                                CONTENT      => $$output_ref,
                                BG_COLOR     => $self->site_content->{bg_color}{value},
                                USER         => $self->user);

    $$output_ref = $self->base_template->output;
}

See also: