Article
CGI::Application: A Simple, Extensible Web Framework
Application Configuration and Database Connections
For the purposes of our application, two other plugins bear further investigation. Firstly, most applications require some kind of configuration. For instance, our application will need to know the database login details so that we can maintain the user information. Secondly, we will need to connect to the database.
CGI::Application::Plugin::ConfigAuto uses the Config::Auto framework. We can place our configuration information in a separate file, and our instance script passes the details to our application package. CGI::Application::Plugin::DBH provides easy DBI access. One of its advantages is that it's lazy-loading, which means that our application doesn't actually connect to the database until we need it to. This is particularly useful because some of our run modes don't require database access.
Firstly, create the configuration file, myapp.cfg:
$cfg{user} = 'myusername';
$cfg{password} = 'mypassword';
$cfg{dsn} = 'dbi:driver:mydb';
$cfg{template_path} = 'templates/';
\%cfg;
Next, we need to make some minor changes to the instance script:
1 #!c:/perl/bin/perl -T
2 use lib 'path/to/my/libs';
3 use strict;
4 use CGI::Carp qw(fatalsToBrowser);
5 use MyApp::User;
6 my $webapp = MyApp::User->new();
7 $webapp->cfg_file('myapp.cfg');
8 $webapp->run();
We have removed the template path from line 6, and have added line 7. Next, we flesh out cgiapp_prerun:
1 sub cgiapp_prerun {
2 my $self = shift;
3 $self->dbh_config($self->cfg('dsn'), $self->cfg('user'),
4 $self->cfg('password'));
5 $self->tmpl_path($self->cfg('template_path'));
6 }
The database handle can now be accessed via the object attribute $self->dbh. So, where we would write the following DBI code in a standard application:
$sth = $dbh->prepare("select column from table");
Instead, we write:
$sth = $self->dbh->prepare("select column from table");
Now we can write the code to create our user:
1 sub _encrypt_password {
2 my $password = shift;
3 my @chars = ('.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z');
4 my $salt = $chars[rand @chars] . $chars[rand @chars];
5 return crypt($password, $salt);
6 }
7
8 sub _create_user {
9 my $self = shift;
10 my $q = $self->query;
11
12 my $encrypted_password = _encrypt_password($q->param('password'));
13
14 my $sql = "insert into app_user (username, password, first_name, last_name,
15 email, receive_newsletter) values (?, ?, ?, ?, ?, ?)";
16
17 $self->dbh->{PrintError} = 0;
18 $self->dbh->{RaiseError} = 1;
19 $self->dbh->do($sql, undef, ($q->param('username'), $encrypted_password,
20 $q->param('first_name'), $q->param('last_name'), $q->param('email'),
21 $q->param('receive_newsletter')));
22 }
Lines 1-6: Encrypt the password -- we don't store it as plain text. This method is also called from _update_user().
Lines 8-22: Create the user in the database.
Line 17: We turn off the printing of errors to standard out, as this would cause CGI::Application to fail. Remember, CGI::Application handles all the output for us, including generating the HTTP headers, so we don't want to interfere with that. We could have set this attribute in the cgiapp_prerun method, but that would require us to connect to the database first, and we don't want to do so unless we specifically need to access data.
Line 18: Whenever a SQL error is encountered we want the application to die, in which case CGI::Application's signal handler will trap the error and send it to the error method defined in the setup.
Lines 19-21: Insert the data.
The code for the remaining run modes is in the zip file that accompanies this tutorial.
Extending CGI::Application
Say, for example, that all of our packages require users to be logged in before they can do anything. We don't want to implement these functions in each package we write. So instead of inheriting from CGI::Application directly, we could write a base class, MyApp::Base, which would sub-class CGI::Application. All of the other packages would inherit from MyApp::Base instead. That is, we would replace:
use base 'CGI::Application';
with:
use base 'MyApp::Base';
Then, in MyApp::Base's cgiapp_prerun method, we could check to see if the user is logged in and, if not, direct them to the log-in page. Because this pre-run method is automatically called prior to executing the requested run mode, it's the ideal place to centralise this kind of logic.
Hint: CGI::Application::Plugin:Session should help you out here. It provides a nice interface to CGI::Session, and makes creating and reading session variables as simple as:
$self->session->param('logged-in', 'y');
And:
return if ($self->session->param('logged-in') eq 'y');
Summary
I hope the benefits of CGI::Application are apparent, but let's summarise some of them:
- CGI::Application calls our run modes for us. This gets rid of a lot of the spaghetti code that developers often write when implementing run mode logic themselves.
- Our code is more maintainable. We can hand over the templates to our HTML designers, while we just worry about the application logic. Remember MVC: the database methods and validation profiles act as our models, the run modes are the controllers, and the templates are our views. Some developers might shuffle or redistribute the responsibility for these components slightly differently -- possibly over more than one package. But in essence we've got a powerful but simple model for developing our applications.
- The framework is extensible. We can extend or over-ride any method we like to suit our needs.
- There is a growing number of plugins, including everything from logging to streaming files. These plugins are often used as wrappers around other modules, but they do much of the heavy lifting for us.
- We're not restricted to CGI scripts. Because almost all of our code is implemented in packages, we can use mod_perl instance scripts instead of the CGI equivalents. In this way, we gain all the benefits of direct access to the Apache innards, as well as super-fast code and persistent database connections.
- There is an active and helpful user community. You can sign up to the email list, or search the archive . There is also a rather fine wiki.
Finally, I'd like to point out that some intrepid developers have ported CGI::Application to other languages. A PHP version is available, and the author provides a simple first-hand case study of its usefulness in his blog. Likewise, any Python programmers should visit http://thraxil.org/code/cgi_app/.