Ron Savage

NAME

CGI::Snapp - An almost back-compat fork of CGI::Application

Synopsis

In general, use as you would CGI::Application, except for the differences discussed in "How does CGI::Snapp differ from CGI::Application?".

But be warned, load_tmp() and tmp_path() in particular are not supported, because they're too tied to the HTML::Template way of doing things, and I prefer Text::Xslate.

Description

A fork of CGI::Application (later CGI::Application::Dispatch etc) in order to understand how they work in sufficient detail that I can put CGI::Snapp etc into production - in my own code - as replacements for those modules.

You are strongly encouraged to peruse "How does CGI::Snapp differ from CGI::Application?" for details of the differences between CGI::Application and CGI::Snapp.

The Flow of Control

This is a short article on which methods get called in which order. Steve Comrie has written a version for CGI::Application: Order of Operations.

An Overview

If you have trouble following this explanation, consider working thru the tests (t/*.pl called by t/test.t) shipped with this distro.

Now, under normal circumstances, your CGI script receives CGI form data and accesses it via an object of type CGI or similar.

Let's say you have a CGI form field called 'rm', and when the user submits the form, that field has the value 'start'.

Then in the terminology of this module, and its predecessor, 'start' is called a run mode.

(In fact, 'rm' is the default name of the CGI form field this module uses to find the name of the run mode. And, when that CGI form field's name does not exist, or is empty, the default run mode is 'start'.)

Then CGI::Snapp uses 'start' to find which method to run to handle that run mode. The default run mode 'start' runs a method called "dump_htm()"' implemented in CGI::Snapp.

How does it use 'start' to find the name of the method? By examining a dispatch table (a hash), which is explained under "run_modes([$option])". 'start' is the key, and (in the simplest case) the value is the name of a method.

Your run mode methods must all return a string or stringref of HTML to be sent to the HTTP client. You code must never write to STDOUT - that's the classic mistake most beginners make.

You can of course override the defaults just mentioned:

o The default CGI form field name 'rm'

Method "mode_param([@new_options])" allows you to change that CGI form field name from 'rm' to another string, amongst other options.

o The default run mode 'start'

Method "start_mode([$run_mode])" allows you to change that run mode 'start' to another string.

o The default association between 'start' and 'dump_html()'

Method "run_modes([$option])" allows you to associate any run mode name with any method name.

The Simple View

So, a basic CGI script is something like:

        #!/usr/bin/env perl

        use KillerApp;
        KillerApp -> new -> run;

Here's what happens as CGI::Snapp runs firstly 'new()' and then 'run()':

o The call to new():

This calls some initialization code, which you never override (so we ignore it), and then calls, in this order:

o 1: cgiapp_init(@args)

Here, @args is the array of options passed in to "new()".

o 2: setup()

These 2 methods give you scope to set up anything you want before your run mode method is activated, by sub-classing CGI::Snapp and re-implementing either or both of these methods.

For instance, if we have this inheritance structure: CGI::Snapp --> parent of --> GlobalApp --> parent of --> SpecificApp, then one or both of these methods could be implemented in GlobalApp and/or in SpecificApp. This would allow yet other descendents of GlobalApp (in parallel with SpecificApp) to share GlobalApp's code, and at the same time implement their own run methods.

After calling "new()", a call to "cgiapp_get_runmode()" will return undef, since determination of the run mode only takes place during the call to "run()".

o The call to run():

This in turn calls:

o 3: mode_param([@new_options])

So now we know how you want run modes to be determined. See "mode_param([@new_options])" for how to control this mechanism.

Then it calls internal code to get the name of the run mode, using - by default - the CGI form field parameter whose name defaults to 'rm'.

Finally, methods are called in this order:

o 4: cgiapp_prerun($run_mode)

During this call (and at no other time), you can call "prerun_mode([$string])" to change the name of the run mode which is about to be executed.

o 5: your_run_mode_method()

This is found via the dispatch table described at length under "run_modes([$option])"

The name of the run mode is the key used to find this method name in the dispatch table (which is just a hash).

Your run mode method must return a string, or a scalarref to a string, containing the HTML to be output to the HTTP client (normally a browser of course).

See note 1 (just below) on what parameters are passed to the method.

See note 2 (just below) on what happens if the key is not present in the dispatch table.

See note 3 (just below) on what happens if the run mode method fails to run.

o 6: cgiapp_postrun(\$html)

A scalarref of the generated HTML is passed in to cgiapp_postrun(), which can overwrite that HTML if desired.

Now, the HTTP headers are generated, and both those headers and the HTML are sent to the HTTP client. You can stop the transmission with "send_output([$Boolean])".

utf8::downgrade() is used to turn off any stray UTF-8 bits on the headers.

o 7: teardown()

Here's where you clean up, by disconnecting from the database, or whatever.

Note 1: Parameters passed to your run mode method

Normally, the only parameter passed is $self, which is an object of type CGI::Snapp or a sub-class.

However, if the method was invoked via the AUTOLOAD mechanism (note 2), the next parameter is the run mode.

Lastly, if the method was invoked via CGI::Snapp::Plugin::Forward's forward(@args), then those parameters you pass to forward() will be passed to the run mode method (after $self).

Note 2: When the run mode is not a key in the dispatch table, this algorithm is invoked

o The AUTOLOAD run mode

The code checks if you have defined a run mode named 'AUTOLOAD'. If so, it's value in the dispatch table is used as the method name.

o Fallback

If no run mode called 'AUTOLOAD' is found, the code calls Carp's croak($message).

Note 3: When the run mode method fails to run, this algorithm is invoked

o The error hook

The method, if any, attached to the 'error' hook is called. The error message generated from the run mode method's failure is passed as the parameter, for you to utilize when deciding what action to take.

Hooks are discussed under "A More Complex View" just below.

o The error_mode method

Next, error_mode([$method_name]) is called. If it returns a defined value, that value is used as the name of a method to call.

o Fallback

Finally, if error_mode([$method_name]) does not return a method name, or calling that method also fails, the code calls Carp's croak($message).

Aren't you glad that was the simple view?

A More Complex View

CGI::Snapp and before it CGI::Application are designed in such a way that some of those methods are actually callbacks aka hooks, and their names are looked up via hook names.

See the Wikipedia article Hooking for a long explanation of hooks.

It works like this: A hook name is a key in a hash, and the corresponding value is a package name, which in turn points to an arrayref of method names. So, for a given hook name and package, we can execute a series of named methods, where those names are listed in that arrayref.

The hooked methods are not expected to return anything.

Here's the default set of hooks aka (default) dispatch table. It's just a hash with fancy values per key:

        my(%class_callback) =
        (
        error          => {},
        forward_prerun => {},
        init           => {'CGI::Snapp' => ['cgiapp_init']},
        prerun         => {'CGI::Snapp' => ['cgiapp_prerun']},
        postrun        => {'CGI::Snapp' => ['cgiapp_postrun']},
        teardown       => {'CGI::Snapp' => ['teardown']},
        );

An explanation:

o Yes, there are class-level callbacks and object-level callbacks

See "add_callback($hook, $option)" for details.

o The error hook

By default, there is no method attached to the 'error' hook. See "error_mode([$method_name])" for details.

o The init hook

Instead of calling cgiapp_init() directly at the start of the run as alleged above, we call all those methods named as belonging to the 'init' hook, of which - here - there is just the default one, CGI::Snapp::cgiapp_init().

o The prerun hook

Likewise.

o The postrun hook

Likewise.

o The teardown hook

Likewise, instead of calling teardown() directly at the finish of the run, we call all those methods named as belonging to the 'teardown' hook, starting with (the default) CGI::Snapp::teardown().

Now, when I say 'all those methods', that's because you can add your own hooked methods, to enhance this process. What happens is that your hooks are pushed onto the stack of hooked methods attached to a given hook name, and run in turn at the appropriate time.

Further, besides extending the stack of methods attached to a pre-existing hook name, you can create new hook names, and attach any number of methods to each.

The pre-defined hooks are called 'error', 'init', 'prerun', 'postrun' and 'teardown', so there is no need to call "new_hook($hook)" for those.

This matter is discussed in depth under the entry for "add_callback($hook, $option)". Also, see "new_hook($hook)" and "call_hook($hook, @args)" for how hooks are named and invoked.

Sample code is in t/callback.pl, in the distro.

Distributions

This module is available as a Unix-style distro (*.tgz).

See http://savage.net.au/Perl-modules/html/installing-a-module.html for help on unpacking and installing distros.

Installation

Install CGI::Snapp as you would for any Perl module:

Run:

        cpanm CGI::Snapp

or run:

        sudo cpan CGI::Snapp

or unpack the distro, and then either:

        perl Build.PL
        ./Build
        ./Build test
        sudo ./Build install

or:

        perl Makefile.PL
        make (or dmake or nmake)
        make test
        make install

Constructor and Initialization

new() is called as my($app) = CGI::Snapp -> new(k1 => v1, k2 => v2, ...).

It returns a new object of type CGI::Snapp.

Key-value pairs accepted in the parameter list (see corresponding methods for details [e.g. "send_output([$Boolean])"]):

o logger => $aLoggerObject

Specify a logger compatible with Log::Handler.

Default: '' (The empty string).

To clarify: The built-in calls to log() all use a log level of 'debug', so if your logger has 'maxlevel' set to anything less than 'debug', nothing will get logged.

'maxlevel' and 'minlevel' are discussed in Log::Handler#LOG-LEVELS and Log::Handler::Levels.

Also, see "How do I use my own logger object?" and "Troubleshooting".

o PARAMS => $hashref

Provides a set of ($key => $value) pairs as initial data available to your sub-class of CGI::Snapp via the "param([@params])" method.

Default: {}.

o send_output => $Boolean

Controls whether or not the HTML output is sent (printed) to the HTTP client.

This corresponds to CGI::Application's use of $ENV{CGI_APP_RETURN_ONLY}. But check the spelling in the next line.

Default: 1 (meaning yes, send). However, if $ENV{CGI_SNAPP_RETURN_ONLY} has a Perlish true value, the default is 0.

Using 0 means you have to get the output from the return value of the "run()" method.

o QUERY => $q

Provides "new()" with a pre-created CGI-compatible object.

Default: ''.

However, a new CGI object is created at run-time if needed. See "query([$q])".

Methods

add_callback($hook, $option)

Adds another method to the stack of methods associated with $hook.

$hook is the name of a hook. $hook is forced to be lower-case.

Returns nothing.

That name is either pre-defined (see "new_hook($hook)") or one of your own, which you've previously set up with "new_hook($hook)".

Sample code:

        # Class-level callbacks.
        $class_name -> add_callback('init', \&method_1);
        KillerApp   -> add_callback('init', 'method_2');

        # Object-level callbacks.
        $app = CGI::Snapp -> new;
        $app -> add_callback('init', \&method_3);

Notes:

o Callback lifetimes

Class-level callbacks outlive the life of the $app object (of type CGI::Snapp or your sub-class), by surviving for the duration of the Perl process, which, in a persistent environment like Starman, Plack, etc, can be long enough to serve many HTTP client requests.

Object-level callbacks, however, go out of scope at the same time the $app object itself does.

o The class hierarchy

Callbacks can be registered by an object, or any of its parent classes, all the way up the hierarchy to CGI::Snapp.

o Callback name resolution

Callback names are checked, and only the first with a given name is called. The type of callback, class or object, is ignored in this test, as it is in CGI::Application. This also means, that if there are 2 callbacks with the same name, in different classes, then still only the first is called.

Consider:

        In Class A: $self -> add_callback('teardown', 'teardown_sub');
        In Class B: $self -> add_callback('teardown', 'teardown_sub');

Here, because the names are the same, only one (1) teardown_sub() will be called. Which one called depends on the order in which those calls to add_callback() take place.

        In Class A: $self -> add_callback('teardown', \&teardown_sub);
        In Class B: $self -> add_callback('teardown', \&teardown_sub);

This time, both teardown_sub()s are called, because what's passed to add_callback() are 2 subrefs, which are memory addresses, and can't be the same for 2 different subs.

o Pre-defined hooks

Only the pre-defined hooks are called by CGI::Snapp. So, if you use your own name in calling new_hook($name), you are also responsible for triggering the calls to that hook.

The pre-defined hooks are called 'error', 'init', 'prerun', 'postrun' and 'teardown', and there is no need to call "new_hook($hook)" for those.

o Class-level callbacks

These belong to the class of the object calling "add_callback($hook, $option)".

o Multiple callbacks for a given hook

If multiple class-level callbacks are added for the same hook by different classes, they will be executed in reverse-class-hierarchy order. That it, the callback for the most derived class is executed first. This is the way normal class-hierarchy overrides work - nothing unexpected here.

If multiple class-level callbacks are added for the same hook by the same class, they will be executed in the order added, since they are pushed onto a stack (as are object-level callbacks).

If multiple object-level callbacks are added for the same hook, they are run in the order they are registered, i.e. in the order of calls to "add_callback($hook, $option)".

o The 'init' hook

Since the 'init' hook is triggered during the call to "new()", even before "setup()" is called, there is no opportunity for normal end-user code (your sub-class of CGI::Snapp) to attach a callback to this hook.

The way around this is to write a class which is not a sub-class of CGI::Snapp, and whose import() method is triggered when you 'use' this class in your sub-class of CGI::Snapp.

There is a group of examples on how to do this. Start with t/hook.test.a.pl, which 'use's t/lib/CGI/Snapp/HookTestA.pm, which in turn 'use's t/lib/CGI/Snapp/Plugin/HookTest1.pm.

Alternately, examine the source code of CGI::Snapp::Plugin::Forward for another way to do things, although it uses 'forward_prerun' rather than 'init'.

To summarize, you are strongly advised to examine t/hook.test.pl and all the modules it uses to gain a deeper understanding of this complex issue. In particular, the order of 'use' statements in your sub-class of CGI::Snapp will determine the order in which class-level callbacks are triggered.

add_header(@headers)

Adds headers to the list which will be sent to the HTTP client.

Returns all headers as a hash.

See also "delete_header(@keys)", "header_add(@headers)", "header_props([@headers])", "header_type([$option])" and "How does add_header() differ from header_add()?".

call_hook($hook, @args)

Call the named hook. $hook is forced to be lower-case.

Returns a hashref of the number of callbacks actually called, where the keys are 'class' and 'object', and the values are integer counts.

@args takes various values, depending on the name of the callback:

o init

Here, @args is the hash of options passed in to "new()".

Defaults to calling CGI::Snapp::cgiapp_init(@args).

o prerun

@args is the name of the run mode.

Defaults to calling CGI::Snapp::cgiapp_prerun($run_mode).

o postrun

@args is a scalarref, where the scalar is the output generated by the run mode method. This scalar does not yet have the HTTP headers attatched (if any).

Defaults to calling CGI::Snapp::cgiapp_postrun(\$html).

o teardown

@args is not used in this case.

Defauts to calling CGI::Snapp::teardown().

If you call an unregistered hook, the call is just ignored.

See "new_hook($hook)" and "add_hook($hook, @args)" if you wish to register a new type of hook.

cgiapp_get_query()

Returns the query object.

This method only creates an object of type CGI when a query object is needed.

Alternately, you can pass your own query object to the "query([$q])" method.

You can override this method in your sub-class, if you wish to provide a CGI-compatible object, such as a CGI::Simple object, or similar. If not using CGI, note:

o The object must have a param() method

Normally, your object just needs to have a "param([@params])" method, for it to be 'similar enough' to a CGI object.

o The object may need a header() method

This is called if "header_type([$option])" returns 'header'.

o The object may need a redirect() method

This is called if "header_type([$option])" returns 'redirect'.

o If you use the 'path_info' option in the call to "mode_param([@new_options])"

In this case the path_info() method will be called on your object.

o If you call "dump_html()", which is the default run mode method for the default run mode 'start'

Lastly, if you don't override the 'start' run mode, the "dump_html()" method (of CGI::Snapp) is called, which in turn calls the Dump() and escapeHTML() methods of your object.

cgiapp_init()

Does nothing. You implement it in a sub-class, if desired.

Defaults to returning nothing.

cgiapp_prerun()

Does nothing. You implement it in a sub-class, if desired.

Defaults to returning nothing.

cgiapp_postrun()

Does nothing. You implement it in a sub-class, if desired.

Defaults to returning nothing.

delete($key)

Deletes a (key => value) pair from the hash of private storage managed by "param([@params])", so a later call to param($key) will return undef.

Returns the value deleted, or undef if $key is absent.

delete_header(@keys)

Deletes headers from the list which will be sent to the HTTP client.

@keys are the names of the headers you wish to delete.

Returns the remaining headers as a hash.

See also "add_header(@headers)", "header_add(@headers)", "header_props([@headers])", "header_type([$option])" and "How does add_header() differ from header_add()?".

dump()

Returns the same string as does "dump_html()", but without any HTML.

dump_html()

Returns a nicely-formatted block of HTML, i.e. a set of paragraphs, containing:

o The run mode
o The query parameters

This is derived from the query object's Dump() method.

o The environment

This is derived from %ENV.

See "cgiapp_get_query()" for how to influence the type of query object used.

error_mode([$method_name])

Sets and gets the name of the error mode method.

Note: This is a method name, not a run mode as is returned from "start_mode([$run_mode])".

Here, the [] indicate an optional parameter.

Default: ''.

Returns the current error mode method name.

forward($run_mode[, @args])

Switches from the current run mode to the given $run_mode, passing the optional @args to the new mode's method.

For this to work, you must have previously called $self -> run_modes($run_mode => 'some_method'), so the code knows which method it must call.

Just before the method associated with $run_mode is invoked, the current run mode is set to $run_mode, and any methods attached to the hook 'forward_prerun' are called.

Calling this hook gives you the opportunity of making any preparations you wish before the new run mode is entered.

Finally, $run_mode's method is called, using @args as its arguments.

Returns the output of the $run_mode's method.

See t/forward.t and t/lib/CGI/Snapp/ForwardTest.pm for sample code.

If you wish to interrupt the current request, and redirect to an external url, then the "redirect($url[, $status])" method is probably what you want.

get_current_runmode()

Returns the name of the current run mode.

header_add(@headers)

Adds and sometimes deletes headers from the list which will be sent to the HTTP client. This strange behaviour is copied directly from CGI::Application.

Returns the remaining headers as a hash.

Deprecated.

See also "add_header(@headers)", "delete_header(@keys)", "header_props([@headers])", "header_type([$option])" and "How does add_header() differ from header_add()?".

get_callbacks($type, $hook)

Gets callback information associated with the given $type (class/object) and $hook.

$type is 'class' for class-level callbacks, and 'object' for object-level callbacks.

Values for $type:

o 'class'

get_callbacks('class', $hook) returns a hashref.

The keys of this hashref are the class names which have registered callbacks for $hook.

The values of this hashref are arrayrefs of method names or references.

o 'object'

get_callbacks('object', $hook) returns an arrayref.

The values of this arrayref are arrayrefs of method names or references.

See t/defaults.pl for sample code.

header_props([@headers])

Sets the headers to be sent to the HTTP client. These headers are expected to be a hash of CGI-compatible HTTP header properties. These properties will be ignored (sic) or passed directly to the header() or redirect() method of the "query([$q])" object, depending on the value returned by "header([$option])".

Returns all headers as a hash.

See also "add_header(@headers)", "delete_header(@keys)", "header_add([@headers])", "header_type([$option])" and "How does add_header() differ from header_add()?".

header_type([$option])

Sets and gets the type of HTTP headers to output.

Here, the [] indicate an optional parameter.

Returns the current header type.

Possible values for $option:

o 'header'

The default. Uses the hash returned by "header_props([@headers])" to generate a set of HTTP headers to send to the HTTP client.

o 'none'

Don't output any headers. See also the "send_output([$Boolean)]" method.

In this case the HTTP status is set to 200.

o 'redirect'

Generates a redirection header to send to the HTTP client.

log($level, $string)

If a logger object exists, then this calls the log() method of that object, passing it $level and $string.

Returns nothing.

So, the body of this method consists of this 1 line:

        $self -> logger -> log($level => $string) if ($self && $self -> logger);

Up until V 1.03, this used to call $self -> logger -> $level($s), but the change was made to allow simpler loggers, meaning they did not have to implement all the methods covered by $level(). See CHANGES for details. For more on log levels, see Log::Handler::Levels.

logger([$logger_object])

Sets and gets the logger object (of type Log::Handler.

Here, the [] indicate an optional parameter.

'logger' is a parameter to "new()". See "Constructor and Initialization" for details.

Also, see "How do I use my own logger object?".

mode_param([@new_options])

Sets and gets the option which defines how to determine the run mode.

Returns the current setting.

Here, the [] indicate an optional parameter.

There are various values which @new_options can take:

o Not specified

Just returns the current setting.

o A string

The value of that string ($new_options[0]) is the name of the CGI form field, and the value of this form field will be the name of the run mode.

So, mode_param('rm') means the CGI form field called 'rm' contains the name of the run mode. This is the default.

o A subref

If $new_options[0] is a reference to a callback (method), call that method when appropriate to determine the run mode.

See t/run.modes.pl's test_7() for an example of this. It uses t/lib/CGI/Snapp/RunModes.pm.

o 2 * N parameters, specified as a arrayref, hashref or array

Here, 2 * N means there must be an even number of parameters, or the code calls Carp's croak($message).

The array is expected to be of the form: (path_info => $integer[, param => $string]).

Use (path_info => $integer) to set the run mode from the value of $ENV{PATH_INFO}, which in turn is set by the web server from the path info sent by the HTTP client. (path_info => 0) means $ENV{PATH_INFO} is ignored. The $integer is explained in full just below.

If the optional (param => $string) part is supplied, then $string will be name of the CGI form field to use if there is no $ENV{PATH_INFO}.

The usage of (path_info => $integer):

Let's say $ENV{PATH_INFO} is 'a/b/c/d/e'. Then here's how to use $integer to select various components of that path info:

o (path_info => 1): 'a' will be the run mode.
o (path_info => 2): 'b' will be the run mode. And so on...
o (path_info => -1): 'e' will be the run mode.
o (path_info => -2): 'd' will be the run mode. And so on...

Summary:

In all cases, the name of the run mode determined - during a call to "run()" - by your chosen mechanism must be a key in the dispatch table (hash) returned by the "run_modes([$option])" method, since that hash is used to find the name of the method to call to process the given run mode. If it's not a key, the code calls Carp's croak($message).

new()

See "Constructor and Initialization" for details on the parameters accepted by "new()".

Returns an object of type CGI::Snapp.

new_hook($hook)

Reserves a slot in the dispatch table for the named hook. $hook is forced to be lower-case.

Returns 1, since that's what CGI::Application does, for some reason.

The pre-defined slots are called 'error', 'init', 'prerun', 'postrun' and 'teardown', so there is no need to call new_hook() for those.

For help populating this slot, see "add_callback($hook, $option)".

param([@params])

Sets and gets application-specific ($key => $value) pairs.

I.e. implements a hash of private storage for your app, which can be initialized via new(PARAMS => {...}) or by calls to param(...).

Here, the [] indicate an optional parameter.

Use this to store special values, and retrieve them later.

Thus, you can at any stage do this:

        $app -> param($key => $value);
        ...
        my($value) = $app -> param($key);

Or, in your CGI script, start with:

        #!/usr/bin/env perl
        use KillerApp;
        my($config_file) = '/web/server/private/config/dir/config.ini';
        KillerApp -> new(PARAMS => {config_file => $config_file}) -> run;

where your config file looks like:

        [template_stuff]
        template_path = /web/server/private/template/dir/web.page.tx
        [other_stuff]
        ...

Then, in the "cgiapp_init()" method, or the "setup()" method, in your sub-class of CGI::Snapp (Config::Tiny's read() returns a hashref):

        use Config::Plugin::Tiny; # Uses Config::Tiny.
        ...
        $self -> param(config => config_tiny($self -> param('config_file') ) );
        ...
        my($template_path) = ${$self -> param('config')}{template_stuff}{template_path};

In this way a set of 4-line CGI scripts with different config file names can run the same code.

Possible values for @params:

o Not specified

Returns an array of the names of the parameters previously set.

        my(@names) = $self -> param;
o 1 parameter

Returns the value of the named parameter, or undef if it has not had a value set.

        my($value) = $self -> param($name);
o 2 * N parameters, specified as a arrayref, hashref or array

Sets the N (key => value) pairs, for later retrieval.

Here, 2 * N means there must be an even number of parameters, or the code calls Carp's croak($message).

Further, if N == 1, returns the value supplied.

        my($value) = $self -> param(key_1 => 'value_1'); # Returns 'value_1'.

        $self -> param(key_1 => 'value_1', key_2 => 'value_2', ...); # Returns undef.

prerun_mode($string)

Set the name of the run mode which is about to be executed.

Returns the current run mode.

prerun_mode($string) can only be called from with your "cgiapp_prerun()" method.

Despite that restriction, "cgiapp_prerun()" can use any information whatsoever to determine a run mode.

For example, it could get parameters from the query object, and use those, perhaps together with config data, to get yet more data from a database.

psgi_app($args_to_new)

Returns a PSGI-compatible coderef which, when called, runs your sub-class of CGI::Snapp as a PSGI app.

$args_to_new is a hashref of arguments that are passed into the constructor ("new()") of your application.

You can supply you own query object, with psgi_app({QUERY => Some::Object -> new}). But really there's no point. Just let the code create the default query object, which will be of type CGI::PSGI.

CGI::Application also provides sub run_as_psgi(), but we have no need of that.

Note: This method, psgi_app(), is very similar to "as_psgi(@args)" in CGI::Snapp::Dispatch, but the latter contains this line (amongst other logic):

        $app -> mode_param(sub {return $rm}) if ($rm);

where the current method does not. This means CGI::Snapp::Dispatch can determine the run mode from the path info sent from the web client, whereas if you use psgi_app(), your sub-class of CGI::Snapp must contain all the logic required to determine the run mode.

query([$q])

Sets and gets the CGI-compatible object used to retrieve the CGI form field names and values. This object also needs to be able to generate HTTP headers. See "header_props([@headers])".

Here, the [] indicate an optional parameter.

Alternately, you can pass in such an object via the 'QUERY' parameter to "new()".

redirect($url[, $status])

Interrupts the current request, and redirects to the given (external) $url, optionally setting the HTTP status to $status.

Here, the [] indicate an optional parameter.

The redirect happens even if you are inside a method attached to the 'prerun' hook when you call redirect().

Specifically, this method does these 3 things:

o Sets the HTTP header 'location' to the given $url
o Sets the HTTP 'status' (if provided) to $status
o Sets the CGI::Snapp header type to 'redirect'

See t/redirect.t and t/lib/CGI/Snapp/RedirectTest.pm for sample code.

If you just want to display the results of another run mode within the same application, then the "forward($run_mode[, @args])" method is probably what you want.

run()

Returns the output generated by the run mode method.

See "send_output([$Boolean])" for controlling whether or not this output is also sent to the HTTP client.

You must call the "run()" method before anything useful can possibly happen. Here is a typical CGI script:

        #!/usr/bin/env perl

        use KillerApp;
        KillerApp -> new -> run;

See "The Flow of Control" for details of the many things which happen during the call to run().

run_modes([$option])

Sets and gets the dispatch table, which is just a hash mapping run mode names to method names.

Returns the dispatch table as a hash.

Here, the [] indicate an optional parameter.

When you call "run()" the code firstly determines the run mode, and then calls run_modes() to get the dispatch table, and then calls a method by getting the method name from the value in this dispatch table corresponding to that run mode.

The parameter list passed to your run mode method is discussed in "The Simple View".

There are 3 values which $option can take:

o An arrayref

This is an abbreviated way of specifying the dispatch table. The arrayref's elements are strings, each of which specifies a run mode and a method of the same name. Hence:

        $app -> run_modes([qw/one two/]);

defines 2 run modes, 'one' and 'two', and these are automatically mapped (by CGI::Snapp) to 2 methods called 'one' and 'two', respectively.

It's very simple, and is, at least at first, probably all you'll need. It just requires you to implement the methods 'one' and 'two' in your sub-class of CGI::Snapp.

o A hashref

Use this to specify both the run modes and their corresponding method names. Thus, something like:

        $app -> run_modes({one => 'sub_1', two => sub {}, three => \&sub_3});

says you'll implement 3 methods: The first is a method called 'sub_1', the second is an anonymous sub, and the 3rd is the named subref.

o A hash

If $option is neither an arrayref nor a hashref, it is assumed to be an array (i.e. a hash!) and treated as though it were a hashref.

Here's how the dispatch table is initialized:

o After calling new()

Since the default start mode is 'start', the dispatch table defaults to (start => 'dump_html'), where the "dump_html()" method is implemented in CGI::Snapp. Of course, you can override that in your sub-class.

o After calling new() and start_mode('first')

This time the dispatch table will still be (start => 'dump_html'), from calling "new()", but now if the code cannot determine a run mode from the CGI parameters, it will default to 'first', which is not in the dispatch table. So, the code calls Carp's croak($message).

That means that if you call "start_mode($run_mode)", it only makes sense if you also call "run_modes([$option])" where $option is {$run_mode => 'some sub name'}.

Lastly, note that calling "run_modes([$option])" does not remove the default (start => 'dump_html') entry from the dispatch table. The code just ignores it. It affects test code, though. See sub test_4 in t/run.modes.pl for instance.

send_output([$Boolean])

Sets and gets the flag which determines whether or not the HTML output generated by your code is actually sent to the HTTP client.

Here, the [] indicate an optional parameter.

The default is 1, meaning yes, send the output to the HTTP client.

During your call to "new()", this code is executed:

        $self -> send_output(0) if ($ENV{CGI_SNAPP_RETURN_ONLY});

which means backward-compatible behaviour is supported for those people wishing to stick with CGI::Application's (negative logic) mechanism to turn off transmission.

And yes, any value which Perl regards as true will suffice for both this method and the value of that environment variable, not just the value 1.

The tests which ship with this mode, for example, almost always turn this flag off to stop output appearing which would confuse the test harness. The one time in testing when the flag is not reset is when I'm testing the default value of this flag.

'send_output' is a parameter to "new()". See "Constructor and Initialization" for details.

setup()

Does nothing. You implement it in a sub-class, if desired.

Defaults to returning nothing.

start_mode([$run_mode])

Sets and gets the name of the run mode to start from.

Returns the current start mode.

Here, the [] indicate an optional parameter.

Default: 'start'.

You're always going to need a start mode, because when your user first sends a request, to, say:

        http://my.web.site/cgi-bin/script.cgi

there is no CGI form data submitted with that request.

So, your code (script.cgi, which uses a sub-class of CGI::Snapp), must determine and execute a run mode (a method) without the user having indicated which run mode to use.

That is, your code must default to something, and the default is a run mode called 'start', which defaults to calling a method called "dump_html()" (within CGI::Snapp).

In other words, in the very simplest case, you don't have to change the name of the initial run mode ('start'), you just have to implement a suitable method, and then call "run_modes([$option])" to tell CGI::Snapp the name of your method.

teardown()

Does nothing. You implement it in a sub-class, if desired.

Defaults to returning nothing.

Typically, teardown() is where you put the code which saves session state, closes logs, disconnects from databases, etc.

You may find it is mandatory for you to override teardown() in your sub-class, especially in persistent environments.

In particular, you are strongly encouraged to read the Data::Session FAQ and the Data::Session Troubleshooting guidelines before writing your own teardown() method.

FAQ

Do I need to output a header when using Ajax?

Yes. At least, when I use jQuery I must do this in a run mode:

        $self -> add_header(Status => 200, 'Content-Type' => 'text/html; charset=utf-8');

        return $self -> param('view') -> search -> display($name, $row);

Here, display() returns a HTML table wrapped in 2 divs in the jQuery style, which becomes the return value of the run mode.

The quoted code is in App::Office::Contacts::Controller::Exporter::Search's display (the run mode), and the display() method being called above is in App::Office::Contacts::View::Search, but it will be the same no matter which Perl app you're running.

Does CGI::Snapp V 1.01 support PSGI?

Yes. See "psgi_app()" and CGI::Snapp::Dispatch.

Is there any sample code?

Yes. See t/*.pl and all the modules in t/lib/*.

See also CGI::Snapp::Dispatch and its t/psi.args.t.

Why did you fork CGI::Application?

In order to study the code. I want to understand how CGI::Application, CGI::Application::Dispatch and CGI::Application::Dispatch::PSGI work in sufficient detail that I can put my forks of those modules into production - in my own code.

Also - obviously - it allows me to implement what I think are code cleanups. And lastly, it allows me to indulge myself in a user-friendly release strategy.

Clearly, those are the same reasons which motivated me to fork CGI::Session into Data::Session.

As a byproduct of forking, rewriting the documentation has also allowed me to cut about 20,000 bytes from the source file Snapp.pm compared to Application.pm.

What version is the fork of CGI::Application based on?

CGI::Snapp V 1.00 is based on CGI::Application V 4.31. CGI::Snapp V 1.01 is based on CGI::Application V 4.50.

How does CGI::Snapp differ from CGI::Application?

My usage of the latter's features was always minimalistic, so - at least initially - I will only support a basic set of CGI::Application's features.

These are the major differences:

Clean up 'run_mode' 'v' runmode

Except for method calls where 'runmode' is unfortunately used, e.g "get_current_runmode()", 'run_mode' and 'run mode' have been adopted as the norm.

Always call croak and not a combination of croak and die

Also, every message passed to croak matches /^Error/ and ends with "\n".

No global variables (except for the inescapable dispatch table of class-level callbacks)

This means things like $$self{__CURRENT_RUNMODE} and $$self{__PRERUN_MODE_LOCKED} etc are only be available via method calls.

Here is a list of the global variables in CGI::Application, and the corresponding methods in CGI::Snapp, in alphabetical order:

o __CALLBACK_CLASSES => %callback_classes
o __CURRENT_RUNMODE => "get_current_runmode()"
o __CURRENT_TMPL_EXTENSION => Not implemented
o __ERROR_MODE => "error_mode([$method_name])"
o __HEADER_PROPS => "header_props([@headers])"
o __HEADER_TYPE => "header_type([$option])"
o __HTML_TMPL_CLASS => Not implemented
o __INSTALLED_CALLBACKS => "installed_callbacks()"
o __IS_PSGI => _psgi()
o __MODE_PARAM => "mode_param([@new_options])"
o __PARAMS => "param([@params])"
o __PRERUN_MODE => "prerun_mode($string)"
o __PRERUN_MODE_LOCKED => _prerun_mode_lock([$Boolean])
o __QUERY_OBJ => "query([$q])"
o __RUN_MODES => "run_modes([$option])"
o __START_MODE => "start_mode([$run_mode])"
o __TMPL_PATH => Not implemented

The leading '_' on some CGI::Snapp method names means all such methods are for the exclusive use of the author of this module.

New methods

o "add_header(@headers)"
o "get_callbacks($type, $hook)"
o "log($level, $string)"
o "logger($logger_object)"
o "send_output([$Boolean])"

Deprecated methods

o "header_add(@headers)"

See "How does add_header() differ from header_add()?".

Unsupported methods

o html_tmpl_class()
o load_tmpl()
o run_as_psgi()
o tmpl_path()

See below for details.

Enchanced features

o Use of utf8::downgrade() to turn off utf8 bit on headers
o Use of Try::Tiny rather than eval

Ideally, this won't be detectable, and hence won't matter.

o call_hook(...) returns a hashref - keys are 'class' and 'object' - of counts of hooks actually called
o delete_header(A list)

See "delete_header(@keys)" for how to delete any number of HTTP headers.

o Calling the error_mode() method

This call is protected by Try::Tiny.

o Calling mode_param([...])

mode_param() can be called with an arrayref, as in $self -> mode_param([qw/path_info -2/]). See t/run.modes.pl for details.

o Calling param([...])

param() can be called with an arrayref, as in $self -> param([qw/six 6 seven 7/]). See t/params.pl for details.

No special code for Apache, mod_perl or plugins

I suggest that sort of stuff is best put in sub-classes.

For the record, I don't use Apache or mod_perl. For web servers I use Engine X, mini_httpd, Starman and (for development) Plack. As it happens, I don't use any plugins (for CGI::Application) either.

So, it's not that I refuse to support them, it's just that I won't put any special code in place unless asked to do so. And then, only if it fits into my philosophy of where this code is headed. And that includes potential re-writes of CGI::Application::Dispatch, CGI::Application::Dispatch::PSGI and CGI::Application::Server.

Upper-case parameters to "new()"

Yes, I know SHOUTING parameter names is ugly, but some back-compat feautures must be supported, right?. Hence "new()" accepts PARAMS and QUERY.

Template Mangement

CGI::Snapp contains no special processing for HTML::Template, or indeed any templating system. Rationale:

There is no support because I see CGI::Application's usage as a manifestation of an (understandable) design fault. If anything, TMPL_PATH should have been CONFIG_PATH.

That is, one of the methods in your sub-class - cgiapp_init(), cgiapp_prerun() or setup(), or a hook - should load a config file, and in that file is the place to put a template path, along with all those other things typically needed: Paths to CSS and Javascript libraries, database connexion parameters, etc.

Then, each different sub-class can load a different config file, if necessary, and hence use a different set of templates. Likewise, testing and production versions of config files can be deployed, and so on.

For example, first read in a hashref of config options (see Config::Plugin::Tiny), and then set up a rendering engine:

        use Config::Plugin::Tiny; # For config_tiny().
        use Text::Xslate;
        ...
        $self -> param
        (
                config => config_tiny('/some/dir/some.file.ini');
        );
        $self -> param
        (
                renderer => Text::Xslate -> new
                (
                input_layer => '',
                path        => ${$self -> param('config')}{template_path},
                )
        );

Then, later, use the renderer like this (in a View component of the MVC style):

        my($output) =
        {
                div     => 'order_message_div',
                content => $self -> param('renderer') -> render('note.tx', $param),
        };

        return JSON::XS -> new -> utf8 -> encode($output);

This module uses Hash::FieldHash, which has an XS component!

Yep.

My policy is that stand-alone modules should use a light-weight object manager (my choice is Hash::FieldHash), whereas apps can - and probably should - use Moose.

How does add_header() differ from header_add()?

Firstly, a note about the name of header_add(). It really should have been called add_header() in the first place, just like add_callback(). After 70 years of programming, programmers should have learned that the verb always and everywhere comes first in function/method/sub names. I do understand the choice of header_add(): It's by analogy with header_props() and header_type(). I used to argue like that myself :-(.

OK, here's how they differ. Consider this code.

        $app -> header_add(a => 1,  b => [2], c => 3,    d => [4])  or call add_header(same params)
        $app -> header_add(a => 11, b => 22,  c => [33], d => [44]) or call add_header(same params)

Output:

        (a => 11,      b => 22,      c => [3, 33], d => [4, 44]) - header_add() - CGI::Snapp and CGI::Application
        (a => [1, 11], b => [2, 22], c => [3, 33], d => [4, 44]) - add_header() - CGI::Snapp

You can see, for both modules, "header_add(@headers)" deletes a pre-exising header when the incoming header's value is a scalar. CGI::Snapp's "header_add(@headers)" emulates CGI::Application's weird "header_add(@headers)" logic here.

But, if you want to add headers without violating the Principle of Least Surprise, use "add_header(@headers)". Also, "delete_header(@keys)" is the counterpart of "add_header(@headers)".

For this reason, "header_add(@headers)" is deprecated.

I'm confused because you called your tests t/*.pl

Well, not really. t/test.t is the test script. It runs all t/*.pl helper scripts. Run it thusly: shell> prove -Ilib -v t/

You can run any single test helper script - e.g. t/defaults.pl - like this: shell> prove -Ilib -v t/defaults.pl

Do you expect authors of plugins for CGI::App to re-write their code?

Nope. But they are free to do so...

Are you going to release any plugins?

Yes. Check out "See Also".

How do I sub-class CGI::Snapp?

There is an example in t/lib/CGI/Snapp/SubClass.pm, reproduced here:

        package CGI::Snapp::SubClass;

        use parent 'CGI::Snapp';
        use strict;
        use warnings;

        use Hash::FieldHash ':all';

        fieldhash my %verbose => 'verbose';

        our $VERSION = '1.00';

        # --------------------------------------------------

        sub _init
        {
                my($self, $arg) = @_;
                $$arg{verbose}  ||= 0; # Caller can set.

                return $self -> SUPER::_init($arg);

        } # End of _init.

        # --------------------------------------------------

        1;

The steps are:

o Create the file

Just copy t/lib/CGI/Snapp/SubClass.pm to get started.

o Declare the accessors

fieldhash my %verbose => 'verbose';

is how it's done. This means you can now have all these features available:

o Use verbose when calling new()
        CGI::Snapp::SubClass -> new(verbose => 1);
o Use verbose() as a getter
        my($verbosity) = $self -> verbose;
o Use verbose($Boolean) as a setter
        $self -> verbose(1);

See t/subclass.pl for how it works in practice.

How do I use my own logger object?

Study the sample code in CGI::Snapp::Demo::Four, which shows how to supply a Config::Plugin::Tiny *.ini file to configure the logger via the wrapper class CGI::Snapp::Demo::Four::Wrapper.

Also, see any test script, e.g. t/basic.pl.

What else do I need to know about logging?

The effect of logging varies depending on the stage at which it is activated.

And, your logger must be compatible with Log::Handler.

If you call your sub-class of CGI::Snapp as My::App -> new(logger => $logging), then logging is turned on at the earliest possible time. This means calls within "new()", to "call_hook($hook, @args)" (which calls cgiapp_init() ) and "setup()", are logged. And since you have probably overridden setup(), you can do this in your setup():

        $self -> log($level => $message); # Log anything...

Alternately, you could override "cgiapp_init()" or "cgiapp_prerun()", and create your own logger object within one of those.

Then you just do $self -> logger($my_logger), after which logging is immediately activated. But obviously that means the calls to call_hook() and setup() (in new() ) will not produce any log output, because by now they have already been run.

Nevertheless, after this point (e.g. in cgiapp_init() ), since a logger is now set up, logging will produce output.

Remember the prefix 'Local::Wines::Controller' mentioned in "PSGI Scripts" in CGI::Snapp::Dispatch?

Here's what it's cgiapp_prerun() looks like:

        sub cgiapp_prerun
        {
                my($self) = @_;

                # Can't call, since logger not yet set up.
                # Well, could, but it's pointless...

                #$self -> log(debug => 'cgiapp_prerun()');

                $self -> param(config => Local::Config -> new(module_name => 'Local::Wines') -> get_config);
                $self -> set_connector; # The dreaded DBIx::Connector.
                $self -> logger(Local::Logger -> new(config => $self -> param('config') ) );

                # Log the CGI form parameters.

                my($q) = $self -> query;

                $self -> log(info  => '');
                $self -> log(info  => $q -> url(-full => 1, -path => 1) );
                $self -> log(info  => "Param: $_: " . $q -> param($_) ) for $q -> param;

                # Other controllers add their own run modes.

                $self -> run_modes([qw/display/]);
                $self -> log(debug => 'tmpl_path: ' . ${$self -> param('config')}{template_path});

                # Set up the database, the templater and the viewer.
                # We pass the templater into the viewer so all views share it.

                # A newer design has the controller created in the db class.

                $self -> param
                        (
                         db => Local::Wines::Database -> new
                         (
                          dbh    => $self -> param('connector') -> dbh,
                          logger => $self -> logger,
                          query  => $q,
                         )
                        );

                $self -> param
                        (
                         templater => Text::Xslate -> new
                         (
                          input_layer => '',
                          path        => ${$self -> param('config')}{template_path},
                         )
                        );

                $self -> param
                        (
                         view => Local::Wines::View -> new
                         (
                          db        => $self -> param('db'),
                          logger    => $self -> logger,
                          templater => $self -> param('templater'),
                         )
                        );

                # Output this here so we know how far we got.

                $self -> log(info  => 'Session id: ' . $self -> param('db') -> session -> id);

        } # End of cgiapp_prerun.

So, should I upgrade from CGI::Application to CGI::Snapp?

Well, that's up to you. Of course, if your code is not broken, don't fix it. But, as I said above, CGI::Snapp will be going in to production in my work.

The biggest problem for you will almost certainly be lack of support for load_tmp() and tmpl_path().

Still, you're welcome to sub-class CGI::Snapp and fix that...

Troubleshooting

It doesn't work!

Hmmm. Things to consider:

o Run the *.cgi script from the command line

shell> perl httpd/cgi-bin/cgi.snapp.one.cgi

If that doesn't work, you're in b-i-g trouble. Keep reading for suggestions as to what to do next.

o Did you try using a logger to trace the method calls?

Pass a logger to your sub-class of CGI::Snapp like this:

        my($logger) = Log::Handler -> new;

        $logger -> add
                (
                 screen =>
                 {
                         maxlevel       => 'debug',
                         message_layout => '%m',
                         minlevel       => 'error',
                         newline        => 1, # When running from the command line.
                 }
                );
        CGI::Snapp -> new(logger => $logger, ...) -> run;

Then, in your methods, just use:

        $self -> log(debug => 'A string');

The entry to each method in CGI::Snapp and CGI::Snapp::Dispatch is logged using this technique, although only when maxlevel is 'debug'. Lower levels for maxlevel do not trigger logging. See the source for details.

o The system Perl 'v' perlbrew

Are you using perlbrew? If so, recall that your web server will use the first line of your CGI script to find a Perl, and that line probably says something like #!/usr/bin/env perl.

So, perhaps you'd better turn perlbrew off and install CGI::Snapp and this module under the system Perl, before trying again.

o Generic advice

http://www.perlmonks.org/?node_id=380424.

See Also

CGI::Application

The following are all part of this set of distros:

CGI::Snapp - A almost back-compat fork of CGI::Application

CGI::Snapp::Dispatch and CGI::Snapp::Dispatch::Regexp - Dispatch requests to CGI::Snapp-based objects

CGI::Snapp::Demo::One - A template-free demo of CGI::Snapp using just 1 run mode

CGI::Snapp::Demo::Two - A template-free demo of CGI::Snapp using N run modes

CGI::Snapp::Demo::Three - A template-free demo of CGI::Snapp using the forward() method

CGI::Snapp::Demo::Four - A template-free demo of CGI::Snapp using Log::Handler::Plugin::DBI

CGI::Snapp::Demo::Four::Wrapper - A wrapper around CGI::Snapp::Demo::Four, to simplify using Log::Handler::Plugin::DBI

Config::Plugin::Tiny - A plugin which uses Config::Tiny

Config::Plugin::TinyManifold - A plugin which uses Config::Tiny with 1 of N sections

Data::Session - Persistent session data management

Log::Handler::Plugin::DBI - A plugin for Log::Handler using Log::Hander::Output::DBI

Log::Handler::Plugin::DBI::CreateTable - A helper for Log::Hander::Output::DBI to create your 'log' table

Machine-Readable Change Log

The file CHANGES was converted into Changelog.ini by Module::Metadata::Changes.

Version Numbers

Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.

Credits

Please read "CREDITS" in CGI::Application and "CONTRIBUTORS" in CGI::Application::Dispatch, since a great deal of work has gone into both of those modules.

Support

Email the author, or log a bug on RT:

https://rt.cpan.org/Public/Dist/Display.html?Name=CGI::Snapp.

Author

CGI::Snapp was written by Ron Savage <ron@savage.net.au> in 2012.

Home page: http://savage.net.au/index.html.

Copyright

Australian copyright (c) 2012, Ron Savage.

        All Programs of mine are 'OSI Certified Open Source Software';
        you can redistribute them and/or modify them under the terms of
        The Artistic License, a copy of which is available at:
        http://www.opensource.org/licenses/index.html



Hosting generously
sponsored by Bytemark