The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

CGI::Application::Demo - A vehicle to showcase CGI::Application

Synopsis

        #!/usr/bin/perl

        use strict;
        use warnings;

        use CGI::Application::Demo;

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

        delete @ENV{'BASH_ENV', 'CDPATH', 'ENV', 'IFS', 'PATH', 'SHELL'}; # For security.

        CGI::Application::Demo -> new() -> run();

Description

CGI::Application::Demo is a vehicle for the delivery of a sample CGI::Application application, with these components:

A set of CGI instance scripts
A set of text configuration files
A CSS file
A data file to help bootstrap populating the database
A set of command line scripts, to bootstrap populating the database
A set of HTML::Templates
A set of Perl modules
CGI::Application::Demo
CGI::Application::Demo::One
CGI::Application::Demo::Two
CGI::Application::Demo::Three
CGI::Application::Demo::Four
CGI::Application::Demo::Five
CGI::Application::Demo::Base
CGI::Application::Demo::Create
CGI::Application::Demo::Faculty
CGI::Application::Demo::LogDispatchDBI

This module, CGI::Application::Demo, demonstrates various features available to programs based on CGI::Application:

Probing a strange environment
Run modes and their subs
Disk-based session handling
Storing the session id in a hidden CGI form field
Using the session to store user-changeable options
Using Class::DBI and Class::DBI::Loader to auto-generate code per database table
Using HTML::Template style templates
Changing the run mode with Javascript
Overriding the default query object

This replaces a CGI object with a lighter-weight CGI::Simple object.

Initialization via a configuration file
Switching database servers via the config file
Logging to a database table
Multiple inheritance, to support MySQL, Oracle and Postgres neatly

See CGI::Application::Demo::LogDispatchDBI.

Note: Because I use Class::DBI::Loader, which wants a primary key in every table, and I use CGI::Session, I changed the definition of my 'sessions' table from this:

        create table sessions
        (
                id char(32) not null unique,
                a_session text not null
        );

to this:

        create table sessions
        (
                id char(32) not null primary key, # I.e.: 'unique' => 'primary key'.
                a_session text not null           # For Oracle, 'text' => 'long'.
        );

compared to what's recommended in the CGI::Session docs.

Also, as you add complexity to this code, you may find it necessary to change line 10 of Base.pm from this:

        use base 'Class::DBI';

to something like this:

        use base $^O eq 'MSWin32' ? 'Class::DBI' : 'Class::DBI::Pg'; # Or 'Class::DBI::Oracle';

Probing a Strange Environment

The five modules One.pm .. Five.pm have been designed so as to be graduated in complexity from simplistic to complex, to help you probe the preculiarities of a strange environment.

Each module ships with a corresponding config file, instance script and template. Well, actually, One.pm and Two.pm are too simple to warrant their own config files, and One.pm does not even need a template.

In each case, you are advised to examine the code in these modules while reading what follows.

Our plan then becomes:

Run cgi-app-lib.cgi

This just tests your usage of 'use lib ...'.

By commenting this out, or not, you can check you're actually finding the system's CGI.pm, or the one you installed.

Of course you can do this for any module, not just CGI.pm.

When that's working, move on.

Use One.pm

Run via cgi-app-one.cgi.

This adds usage of a module based on CGI::Application, but the module itself has, deliberately, no complexity of its own. It simple display a build-in web page.

When that's working, move on.

Use Two.pm

Run via cgi-app-two.cgi.

This adds:

Replacing CGI with CGI::Simple

See sub cgiapp_get_query().

Using HTML::Template-style templates

See sub cgiapp_init() and sub start().

When that's working, move on.

Use Three.pm

Run via cgi-app-three.cgi.

This adds:

Using CGI::Application::Plugin::Config::Context

Here for the first time we read a config file.

Naturally, you'll need to edit the config file to suit your environment.

When that's working, move on.

Use Four.pm

Run via cgi-app-four.cgi.

This adds:

Using a CSS file
Getting a CSS's url from the config file
Getting a DSN, username, password and attributes from the config file

Now we're testing a more complex config file.

Use DBI

And we use those parameters to test a direct connexion to the database.

We use this connexion to display all records in the faculty table, using the CSS's url.

The faculty table has no purpose other than to provide data to be displayed, either via DBI or via Class::DBI.

When that's working, move on.

Use Five.pm

Run via cgi-app-five.cgi.

WARNING

But first a warning. Base.pm is used by both Five.pm and Demo.pm. You must edit line 77 of Base.pm to say either 'cgi-app-five.conf' or 'cgi-app-demo.conf', depending on which module you are testing.

Five.pm adds:

Using a base module, Base.pm, for all table modules

Actually, there's only per-table module, Faculty.pm, at this time, but at least you can see how to use a base module to share code across table modules.

Using a dedicated module for the faculty table: Faculty.pm
Using Class::DBI::Loader

This uses Class::DBI to automatically load a module-per-table. DBIx::Class provides similar features, but I've never used it.

As above, we just display all records in the faculty table.

By now, if successful, you will have tested all the components of Demo.pm, one-by-one.

So, the next step is obvious...

Use Demo.pm

Run via cgi-app-demo.cgi.

This adds

Using CGI::Application::Plugin::LogDispatch

Now we log things to a database table via LogDispatchDBI.pm (below).

Using CGI::Application::Plugin::Session

Now we use sessions stored in the database via CGI::Session.

Install my module CGI::Session::Driver::oracle, if necessary.

Create.pm

The code to drop tables, create tables, and populate tables is all in this module.

This was a deliberate decision. For example, when everything's up and running, there is no need for your per-table modules such as Faculty.pm to contain code to do with populating tables, especially constant tables (as faculty is in this demo).

Base.pm

A module to share code between all per-table modules.

Faculty.pm

A module dedicated to a specific table.

LogDispatchDBI.pm

A module to customize logging via Log::Dispatch::DBI.

Distributions

This module is available both as a Unix-style distro (*.tgz) and an ActiveState-style distro (*.ppd). The latter is shipped in a *.zip file.

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

Order of Execution of subs within a CGI::Application-based script:

The instance script

The instance script (see Synopsis) contains 'use CGI::Application::Demo', which causes Perl to load the file /perl/site/lib/CGI/Application/Demo.pm.

At this point the instance script is initialized, in that package CGI::Application::Demo has been loaded. The script has not yet started to run.

This package contains "use base 'CGI::Application'", meaning CGI::Application::Demo is a descendent of CGI::Application. That is, CGI::Application::Demo is-a CGI::Application.

This (CGI::Application::Demo) is what I'll call our application module.

What's confusing is that application modules can declare various hooks (a hook is an alias for a sub) to be run before the sub corresponding to the current run mode. Two of these hooked subs are called cgiapp_init() (hook is 'init'), and cgiapp_prerun() (hook is 'prerun').

Further, a sub prerun_mode() is also available.

None of these 3 sub are called yet, if at all.

The instance script, revisited

Now CGI::Application::Demo -> new() is called, and it does what it has to do.

This is, it initializes a new object of type CGI::Application.

This includes calling the 'init' hook (sub cgiapp_init() ) and sub setup(), if any.

Since we did in fact declare a sub cgiapp_init() (hook is 'init'), that gets called, and since we also declared a sub setup(), that then gets called too.

You can see the call to setup() at the very end of CGI::Application's sub new().

Oh, BTW, during the call to cgiapp_init, there was a call to sub setup_db_interface(), which, via the magic of Class::DBI::Loader, tucks away an array ref of a list of classes, one per database table, in the statement $self -> param(cgi_app_demo_classes => $classes), and an array ref of a list of table names in the statement $self -> param(cgi_app_demo_tables => $tables).

The instance script, revisited, again

Now CGI::Application::Demo -> run() is called.

First, this calls our sub cgiapp_get_query() via a call to sub query(), which we declared in order to use a light-weight object of type CGI::Simple, rather than an object of type CGI.

Then, eventually, our application module's run mode sub is called, which defaults to sub start().

So, sub start() is called, and it does whatever we told it to do. The app is up and running, finally.

Required Modules

Carp
CGI::Application
CGI::Application::Plugin::Config::Context
CGI::Application::Plugin::LogDispatch
CGI::Application::Plugin::Session
CGI::Simple
Class::DBI
Class::DBI::Loader
Config::General
HTML::Template
Log::Dispatch::DBI

Prerequisites of the Required Modules

This list has been moved into a separate document:

http://savage.net.au/Perl-modules/html/modules-for-a-new-pc.html

Installing the non-Perl components of this module

Unpack the distro, and you'll see various directories to be moved to where your web server can find them.

$distro/cgi-bin/cgi-app-demo/

These are CGI scripts.

$distro/conf/cgi-app-demo/

These are config files.

$distro/css/cgi-app-demo/

This is the one CSS file.

$distro/templates/cgi-app-demo/

These are the templates.

Now you may have to edit a line or two in some files.

I realise all this seems to be a bit of an effort, but once you appreciate the value of such configuation options, you'll adopt them as enthusiastically as I have done. And you only do this once.

Here I just list the lines you should at least consider editing. Similar comments apply to all *.conf and *.pm files.

cgi-app-demo.conf
        css_url=/css/cgi-app-demo/cgi-app-demo.css
        dsn=dbi:mysql:cgi_app_demo, username and password
        session_driver=driver:Oracle
        tmpl_path=/apache2/htdocs/templates/cgi-app-demo/
Demo.pm
        my($config_file) = ...;
Base.pm
        my($config_file) = ...;
cgi-bin/cgi-app-demo/cgi-app-demo.cgi

Patch the 'use lib' line if you've installed your modules in a non-standard location.

$distro/scripts/test-conf.pl

Patch, if necessary:

        my($config_file) = "$ENV{'ASSETS'}/conf/cgi-app-demo/cgi-app-demo.conf";
$distro/scripts/drop.pl, create.pl and populate.pl

In these, you need to set the environment variables (which are not used by *.cgi):

ASSETS=/apache2
INSTALL=/perl/site/lib

Initializing the Database

Lastly, cd $distro/scripts/ and create and populate the database:

perl drop.pl
perl create.pl
perl populate.pl

And finish off with...

test-conf.pl

Now test http://127.0.0.1/cgi-bin/cgi-app-demo/cgi-app-lib.cgi, and each of cgi-app-(one,two,three,four,five).cgi in turn.

Finally, point your web client at http://127.0.0.1/cgi-bin/cgi-app-demo/cgi-app-demo.cgi and see what happens.

A Note about HTML::Entities

In general, a CGI::Application-type app could be outputting any type of data whatsoever, and will need to protect that data by encoding it appropriately. For instance, we want to stop arbitrary data being interpreted as HTML.

The sub HTML::Entities::encode_entities() is designed for precisely this purpose. See that module's docs for details.

Now, in order to call that sub from within a double-quoted string, we need some sort of interpolation facility. Hence the module HTML::Entities::Interpolate. See its docs for details.

This demo does not yet need or use HTML::Entities::Interpolate.

Test Environments

I've tested these modules in these environments:

GNU/Linux, Perl 5.8.0, Oracle 10gR1, Apache 1.3.33
GNU/Linux, Perl 5.8.0, Postgres 7.4.7, Apache 2.0.46
Win2K, Perl 5.8.6, MySQL 4.1.9, Apache 2.0.52

Credits

I drew significant inspiration from code in the CGI::Application::Plugin::BREAD project:

http://charlotte.pm.org/kwiki/index.cgi?BreadProject

I used those ideas to write my own bakermaker, the soon-to-be-released (Dec '05) DBIx::Admin.

In fact, the current module is a cut-down version of DBIx::Admin.

Author

CGI::Application::Demo was written by Ron Savage <ron@savage.net.au> in 2005.

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

Copyright

Australian copyright (c) 2005, Ron Savage. All rights reserved.

        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