http://qs1969.pair.com?node_id=622071

A Beginners Guide to CGI::Application

CGI::Application is a framework for building web applications in Perl. I discovered it a few months ago while browsing perl.com (see Rapid Website Development with CGI::Application) and decided to give it a try on my next project.

There are a large number of plugins available, which makes it easy to do almost anything you wish. I was able to quickly find plugins for session management, database access, authentication, etc. However, the sheer volume of options quickly became confusing. I had trouble figuring out how to best put all the seperate parts together into one big (working) piece.

Of course, there are examples out there ( see the CGI::Application wiki) - but I couldn't find anything exactly like what I wanted to do. Furthermore, I wanted to use SSL for secure authentication, and couldn't find any info on that documented anywhere. So, after much time and effort, I'm presenting this small, working example in order to help anyone else that might come along and find themselves in the same situation.

I don't consider myself an expert, and I don't claim that this is the only way to do it, nor the best way to do it. But it does work, and I think one could use this as a starting point upon which to build. After reading all the necessary module documentation on CPAN, you should be able to modify this as necessary to suit your own needs.

Here's a list of the modules that I use in this tutorial:

The finished example demonstrates a simple CGI login page. Authentication is performed against a MySQL database. The session ID is stored as a cookie on the client side (browser). There are public pages anyone can see, and private pages one must login to see. The login form uses SSL to provide greater security.

I'm going to assume you already have apache installed and configured properly. If you need help with this part, try looking here: apache docs. I'm also going to assume you have MySql installed and configured properly. If you need help with that, try looking here: mysql docs. I'm also going to assume that you've installed all the necessary perl modules on your system. If you need help with that, try looking here: Modules: How to Create, Install, and Use .

If you've made it this far - congratulations! Now let's get started.

Database Setup

The database used here contains two tables. The first table will be used to store user names and passwords. The second table will be used to store session data. Having sessions allows data to persist over time. For example, if you develop a shopping cart application, and someone places an item in the cart, the session will 'remember' the cart's contents. For this tutorial, we'll only be remembering if someone is logged in or not.

First, login to mysql as the root user:

mysql -u root -p

Then type the following commands at the mysql prompt:

create database webapp;
grant all on webapp.* to webadmin@'localhost';
flush privileges;
quit

Now login to mysql as user 'webadmin':

mysql -u webadmin webapp

Enter this to create our 'user_info' table:

create table user_info (
  username varchar(64) not null,
  password varchar(64) not null
);

Enter this to insert a user into the table:

insert into user_info (username, password) values (
  'username',
  '5f4dcc3b5aa765d61d8327deb882cf99'
);

Note that '5f4dcc3b5aa765d61d8327deb882cf99' is the MD5 hexhash of the word 'password'.

Next, create the table for holding session data:

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

That's it! You can quit mysql. It's time to write some perl scripts.

CGI-BIN Scripts

I'm going to assume that your cgi-bin directory is located at this path:

/var/www/cgi-bin

If for some reason yours is in a different location, that's okay, you'll just have to change the path appropriately in the instructions below.

First, I'm going to create a directory for my application, called WebApp. Inside this directory, I'll create a script called simple.pl. Just to make sure everything is working, put the following code inside simple.pl:

#!/usr/bin/perl print "content-type: text/html\n\n"; print "hello world\n";

Now, if you go to this URL:

http://localhost/cgi-bin/WebApp/simple.pl

You should see the message "hello world". If you do not, then don't read any further until you get that working. Check your apache config file and be sure to 'chmod 755' the WebApp directory and the simple.pl script.

Inside the WebApp directory, create a 'templates' subdirectory, and a 'libs' subdirectory. Next create a 'MyLib' directory inside of 'libs'.

Line-by-Line Discussion of Code Listings

The code listings to follow have each line numbered for easy reference. If you'd like to download these files and try running them yourself, use the following perl one-liner to remove the numbers from the start of each line:

perl -p -i.orig -e 's/^=\s+\d+\s+= ?//' simple.pl

In this particular case, 'simple.pl' will be edited in place (line numbers removed), and the original file backed up as 'simple.pl.orig'.


The file 'simple.pl' (Listing 1) is the application script. Line 3 tells perl where to look for my custom module files. Line 4 loads the module that actually defines the run modes (pages) for my application, and line 12 starts it running. Lines 7-10 set some instance-specific run parameters. In this case, we're defining 'simple.ini' to be our configuration file (Listing 2). It's basically just name = value pairs. Using these will help us avoid hard-coding anything in our scripts that may need to be changed. For now, all that's in here is info needed for establishing a connection to the database.

Normally, you would create a web app by inheriting from class CGI::Application. In this case, class MyLib::Simple actually inherits from MyLib::Login. MyLib::Login inherits from CGI::Application. This lets us put all of the login/logout related functions into a seperate module. Plus, if we later develop a second web app (MyLib::Simple2, for example) it can easily reuse all of the same login code. Imagine having to maintain 10 different web apps. Let's say you want to switch databases from MySql to Postgres. This approach allows you to modify one single Login.pm module, rather than 10 seperate app-specific modules. See? Code reuse is good!

Since the first thing Simple.pm does is load Login.pm (line 22), let's start with Login.pm (Listing 4). We'll come back to Listing 3 later.

Line 97 loads CGI::Application. Lines 99-104 load all of the plugin modules we want to use. Here's a quick overview of what each one does:

'AutoRunmode' will allow us to use shorter URLs to refer to our web pages. For example, instead of this:

http://localhost/cgi-bin/WebApp/simple.pl?rm=index

we can instead use this:

http://localhost/cgi-bin/WebApp/simple.pl/index

In other words, it will allow us to extract the desired run mode from the PATH_INFO environment variable. It also lets us define run mode methods using attributes, like this:

sub mypage : Runmode {

Otherwise, we'd have to use the runmodes() method to register each page we want to define. This would normally be done in the setup method, but since we're using AutoRunmode, our setup method (lines 107-114) is pretty short.

The DBH plugin gives us easy access to perl's DBI module (database interface).

The Session plugin is a wrapper around CGI::Session. This will help us maintain state from one page view to the next (in other words, it helps us provide persistent data).

The Authentication module provides methods for logging in and out. Let's say we have 50 people browsing our site at the same time, and each one has placed items in their shopping cart. Authentication allows us to identify individual users. Otherwise, there would be no way to determine which cart belongs to which user.

The ConfigAuto module helps us read parameters from our config file.

Line 105 loads the MD5 module, which we'll use later to encrypt passwords.

Lines 107-114 define our setup method. The most important thing here is line 111, which tells CGI::Application to parse the PATH_INFO environment variable. The AutoRunmode plugin needs this to work properly.

Line 116 begins our cgiapp_init method. As the name implies, this is where we do most of our 'initialization'. Line 119 uses the cfg method from the ConfigAuto plugin to read our config file and store all our name-value pairs into a hash called %CFG.

Line 121 tells CGI::Application where to look for template files (HTML::Template will need to know this later on).

Lines 124-128 initialize our database connection. The dbh_config method is defined by the DBH plugin. Rather than hard-code the necessary parameters, we're giving it the data we read from our config file. Note that this lets us avoid hard coding our mysql password inside our script. If we need to run our web app on several different servers, we might have a different database on each. In this case, we'd have a different config file for each server, but the perl code would be the same for all of them.

Lines 130-142 initialize the session configuration. There are lots of different ways to do this. You could, for example, choose to save session data to a file instead of to a database. Or you could choose to use Data::Dumper to serialize your data instead of Storable. Or, you could use Postgres instead of MySql. But in this case, I've decided to use MySql, so I specify the 'mysql' driver. Storable is a good serializer because it's fast and uses compression, but the result is not humanly readable like Data::Dumper. If you run mysql and type 'select * from sessions' you'll see lots of strange characters! So Data::Dumper might be better if you're trying to debug problems with your database, but the two formats are incompatible, so it's not easy to switch back and forth between the two (if you want to swap methods, I'd recommend dropping the 'sessions' table and recreating it each time).

In Line 133, self->query will return the current CGI object, and $self->dbh will return the current database handle (the one we just configured in lines 124-128).

Line 136 defines the default expiration time for our session. This means that once someone logs in, they will automatically be logged out after 1 hour of inactivity.

Each session created will be assigned a unique 32 character id code. This id will be written into a browser cookie. Lines 137-141 show how to configure cookie-related parameters. I've commented these out, because I prefer to use the defaults.

Lines 145-160 configure parameters for the Authentication plugin. In line 146, we specify the DBI driver because we're using a database. Line 147 gives the Authentication plugin a copy of our database handle. Line 148 gives it the name of the table to use for finding usernames and passwords. Lines 150 and 151 define which columns to use within that table. Line 151 also specifies that passwords will be encrypted using MD5. Line 155 says that we'll save our login state inside a session. If a user is not logged in, but tries to access a protected page, the Authentication plugin will automatically redirect the user to the login page. Once the user enters a valid username and passsword, they get redirected back to the protected page they originally requested. If a session expires, they get automatically logged out. For this to work properly, the Authentication plugin has to know which methods to call to perform the login/logout functions. These are defined in lines 156-157.

For added security, I wanted to use SSL to prevent a password entered on the login page from being transmitted over the internet as plain text. So I make sure the login page is accessed using https, rather than http (more on this later). But https is generally slower than http, so once logged in, I wanted to switch back to regular http. There's no easy way to do this! My work-around is to introduce a post-login run mode (line 158). This is a run mode that gets called after a user successfully logs in. What does this special run mode do? It basically figures out which run mode (page) the user really wanted to see, then redirects the browser to that page using http (not https).

Line 159 specifies a subroutine to use to generate a login form. Note that the Authentication plugin comes with a default form that you can use. I'm including this one just to demonstrate how to go about creating one of your own, in case you really want to. The default one actually looks much better than mine, so you might wish to comment out lines 157-159 in order to see it! The best solution is to write a custom login form of your own, that looks the way you want. The one I present here is intended only to demonstrate the basic functionality.

Lines 163-165 define which runmodes require a successful login. The Login.pm module doesn't define any content - all of the actual web pages are in Simple.pm. So I define the 'mustlogin' page here as a kind of place-holder. It's a dummy page that forces you to login, but immediately redirects you back to the default start page (usually the index page). The mustlogin runmode is defined in lines 174-178.

The teardown method (lines 169-172) is used to close the database connection.

The 'okay' method (lines 180-192) exists only to switch from https back to http. It assumes that the target run mode is stored in a cgi parameter named 'destination', but if for some reason this is not the case, it will default back to the index page.

The login method (lines 194-213) basically just displays the login form (line 211). But first, it checks to make sure you're not already logged in (lines 198-204), and second, it makes sure you're connecting with https. If you try to access the login page with http, it will automatically redirect you using https.

The login form is generated by my_login_form (lines 215-238). Actually, most of the form is pregenerated in the form of a template (see Listing 7). Line 217 loads this template, lines 234-236 insert values into the template parameters, and line 237 generates the final HTML. The 'destination' parameter is important, because it contains the URL of the page to go to once the user has successfully logged in. This gets tricky, because there are two ways to log in. On the index page, there's a link which says "click here to log in". If you click that link, and log in successfully, you'll be taken back to the index page. If you try to access a protected page before logging in, you'll automatically get redirected to the login page, but it will use the 'destination' parameter to remember where you were trying to go, and take you there once you do login.

So, my_login_form first tries to get a value for 'destination' from the CGI query object (in case it was passed as a hidden variable). If that fails, it tries looking at the PATH_INFO environment variable (in case it's being passed as part of the URL). If all else fails, it defaults to the index page. In the event the login attempt fails, you get redirected back to the login form, where it asks you to try again.

Adding SSL into this process gets tricky. The login method will ensure that the login page has a URL like this:

https://localhost/cgi-bin/WebApp/simple.pl/login

But for the username/password data to be encrypted, it's important that the page that this form gets submitted to also have an https in the URL. So if you look at line 341 (inside the login_form.html template) you'll see the "action" part of the form tag is set to point to the 'mustlogin' method. But once you DO successfully login, the Authentication plugin is going to run the post-login run mode 'okay', which redirects us back to the indended destination, minus the https URL.

Note that depending on which browser you're using, and what security settings you have enabled, you may see popups asking you to accept a security certificate, or that you're entering/leaving a secure area, etc. These are all normal and can be safely ignored.

Lines 240-247 define the logout method. Note that logging out actually deletes the current session (see line 244).

Lines 249-257 define the default error run mode: if any run mode fails to eval, CGI::Application will redirect you to this run mode. It gives you a nice way to trap errors in a sane way.

Lines 259-268 define the AUTOLOAD method. This will get called if you try to access a non-existant run mode. Having this in place gives a user a nice error message if they accidentally type in a URL wrong.

Now we're ready to return to Listing 3. Since MyLib::Simple inherits from MyLib::Login, it has access to all the methods we've just discussed, in addition to all the methods defined by CGI::Application.

Lines 24-35 define cgiapp_init. Line 27 calls the cgiapp_init method we saw before, in Login.pm. The "SUPER::" tag means "call this method from my parent class". We need to do this because we need the database connection, session config, etc. like before - but we also want to do some additional, application specific initialization. For example, Simple.pm defines two private run modes (pages that require a user to login in order to view). We specify those in lines 30-33. But without line 27, the cgiapp_init in Simple.pm would over-ride the one in Login.pm, and be run INSTEAD of that one, not IN ADDITION to that one, like we want.

So just remember, every web app we create can inherit from Login.pm to get the same login functionality, and each will have its own cgiapp_init to do "local initialization", but each will need to call SUPER::cgiapp_init to do Login.pm's "global initialization" stuff (database connect, session config, etc.). The alternative is to copy & paste the code each time, but then if you ever want to make a change, you'll have to change it in each copy. This way you can simply change it in one place and globally effect every application that depends on it (for example, if you decide you'd rather use SHA1 instead of MD5 - just change Login.pm).

Lines 37-46 define our index page. Notice that it has the attribute "StartRunmode" instead of "Runmode". That means it will be the page loaded if no other page is specified. So these three URLs are all equivalent:

http://localhost/cgi-bin/WebApp/simple.pl?rm=index
http://localhost/cgi-bin/WebApp/simple.pl/index
http://localhost/cgi-bin/WebApp/simple.pl

The first URL explicitly sets the runmode to 'index', the second URL sets the run mode to 'index' via the PATH_INFO environment variable, and the third URL defaults to 'index' as the start run mode because no other mode was specified.

So what does the index run mode do? It loads the index.html template (Listing 5, line 39), populates a few template parameters (lines 41-43) - note that you can define several values inside a single param statement - then returns the final HTML for rendering (line 45).

Note that we call $self->authen->username. If someone is logged in, this will return their username. Otherwise, it will return null. We can test the USER value inside the template to display different messages accordingly (lines 292-297). So a user that is NOT logged in will see a "click here to login" message, and a user that IS logged in will see a "click here to log out" message.

Finally, the index page displays links to other pages: two public, and two private. All of these pages use the same default.html template (Listing 6), but the parameters like NAME and MESSAGE vary for each one.

If you'd like to test the "error run mode", uncomment line 66 then try clicking the link for "public2". The "die" statement will generate an error message, and the error run mode will trap it.

To test the "autorunmode", try asking for a nonexistant page, like this:

http://localhost/cgi-bin/WebApp/simple.pl/bogus

You should be able to visit pages public and punlic2 without logging in. If you click on the link for private or private2, you'll get redirected to the login page (URL will switch to https), once you submit your username and password you'll get redirected to the private page (the URL will go back to http). If you click on "login" first, you'll be taken back to the index page. The, if you click on a link to a private page, it will take you there directly. If you do nothing for 1 hour, then try to again access a private page, it will ask you to login again (because the session will have expired).

Note that the templates contain a < META > tag with an expiration date several years old. This is a hack to trick the browser into NOT caching the page. Otherwise, you might still be able to see a private page after logging out, because the browser will pull it from the local page cache. If you force the page to reload, you should be prompted to login again.

Note that if this were a real application, the Login.pm module would include a "register" run mode to add new users, a "reset" method to let users change their password, a "forgot" method in case someone can't recall their password, etc.

I'll leave that as an exercise for the reader. The tricky part is logging in and out and getting the SSL to work, and hopefully I've been able to help with that.


update: corrected typo in perl one-liner. Also corrected typo regarding default login form.
update: corrected typo in 'templates' directory name.

Listing 1: WebApp/simple.pl

(go back)
= 1 = #!/usr/bin/perl = 2 = use strict; = 3 = use lib '/var/www/cgi-bin/WebApp/libs'; = 4 = use MyLib::Simple; = 5 = = 6 = my $webapp = MyLib::Simple->new( = 7 = PARAMS => { = 8 = cfg_file => ['simple.ini'], = 9 = format => 'equal', = 10 = }, = 11 = ); = 12 = $webapp->run(); = 13 =

Listing 2: WebApp/simple.ini

(go back)
= 14 = DB_DSN = dbi:mysql:database=webapp = 15 = DB_USER = webadmin = 16 = DB_PASS = = 17 =

Listing 3: WebApp/libs/MyLib/Simple.pm

(go back)
= 18 = package MyLib::Simple; = 19 = use strict; = 20 = = 21 = use lib '/var/www/cgi-bin/WebApp/libs'; = 22 = use base 'MyLib::Login'; = 23 = = 24 = sub cgiapp_init { = 25 = my $self = shift; = 26 = = 27 = $self->SUPER::cgiapp_init; = 28 = = 29 = # define runmodes (pages) that require successful login: = 30 = $self->authen->protected_runmodes( = 31 = 'private', = 32 = 'private2', = 33 = ); = 34 = = 35 = } = 36 = = 37 = sub index : StartRunmode { = 38 = my $self = shift; = 39 = my $template = $self->load_tmpl("index.html"); = 40 = $template->param({ = 41 = NAME => 'INDEX', = 42 = MYURL => $self->query->url(), = 43 = USER => $self->authen->username, = 44 = }); = 45 = return $template->output; = 46 = } = 47 = = 48 = sub public : Runmode { = 49 = my $self = shift; = 50 = my $template = $self->load_tmpl("default.html"); = 51 = my $msg = "You can see this without logging in.<br>"; = 52 = $template->param(MESSAGE => $msg); = 53 = $template->param(NAME => 'Public'); = 54 = $template->param(MYURL => $self->query->url); = 55 = return $template->output; = 56 = } = 57 = = 58 = sub public2 : Runmode { = 59 = my $self = shift; = 60 = my $template = $self->load_tmpl("default.html"); = 61 = my $msg = "You can see this without logging in.<br>"; = 62 = $template->param(MESSAGE => $msg); = 63 = $template->param(NAME => 'Public2'); = 64 = $template->param(MYURL => $self->query->url); = 65 = = 66 = ### die "made it here\n"; = 67 = = 68 = return $template->output; = 69 = } = 70 = = 71 = sub private : Runmode { = 72 = my $self = shift; = 73 = my $template = $self->load_tmpl("default.html"); = 74 = my $msg = "You must log in to see this."; = 75 = $template->param(MESSAGE => $msg); = 76 = $template->param(NAME => 'Private'); = 77 = $template->param(MYURL => $self->query->url); = 78 = return $template->output; = 79 = } = 80 = = 81 = sub private2 : Runmode { = 82 = my $self = shift; = 83 = my $template = $self->load_tmpl("default.html"); = 84 = my $msg = "You must log in to see this."; = 85 = $template->param(MESSAGE => $msg); = 86 = $template->param(NAME => 'Private2'); = 87 = $template->param(MYURL => $self->query->url); = 88 = return $template->output; = 89 = } = 90 = = 91 = = 92 = 1; = 93 =

Listing 4: WebApp/libs/MyLib/Login.pm

(go back)
= 94 = package MyLib::Login; = 95 = = 96 = use strict; = 97 = use base 'CGI::Application'; = 98 = = 99 = use CGI::Application::Plugin::AutoRunmode; = 100 = use CGI::Application::Plugin::DBH(qw/dbh_config dbh/); = 101 = use CGI::Application::Plugin::Session; = 102 = use CGI::Application::Plugin::Authentication; = 103 = use CGI::Application::Plugin::Redirect; = 104 = use CGI::Application::Plugin::ConfigAuto (qw/cfg/); = 105 = use Digest::MD5 qw(md5_hex); = 106 = = 107 = sub setup { = 108 = my $self = shift; = 109 = = 110 = $self->mode_param( = 111 = path_info => 1, = 112 = param => 'rm', = 113 = ); = 114 = } = 115 = = 116 = sub cgiapp_init { = 117 = my $self = shift; = 118 = = 119 = my %CFG = $self->cfg; = 120 = = 121 = $self->tmpl_path(['./templates']); = 122 = = 123 = # open database connection = 124 = $self->dbh_config( = 125 = $CFG{'DB_DSN'}, # "dbi:mysql:database=webapp", = 126 = $CFG{'DB_USER'}, # "webadmin", = 127 = $CFG{'DB_PASS'}, # "" = 128 = ); = 129 = = 130 = $self->session_config( = 131 = CGI_SESSION_OPTIONS => [ = 132 = "driver:mysql;serializer:Storable;id:md5", = 133 = $self->query, {Handle => $self->dbh}, = 134 = ], = 135 = = 136 = DEFAULT_EXPIRY => '+1h', = 137 = # COOKIE_PARAMS => { = 138 = # -name => 'MYCGIAPPSID', = 139 = # -expires => '+24h', = 140 = # -path => '/', = 141 = # }, = 142 = ); = 143 = = 144 = # configure authentication parameters = 145 = $self->authen->config( = 146 = DRIVER => [ 'DBI', = 147 = DBH => $self->dbh, = 148 = TABLE => 'user_info', = 149 = CONSTRAINTS => { = 150 = 'user_info.username' => '__CREDENTIAL_1__', = 151 = 'MD5:user_info.password' => '__CREDENTIAL_2__' = 152 = }, = 153 = ], = 154 = = 155 = STORE => 'Session', = 156 = LOGOUT_RUNMODE => 'logout', = 157 = LOGIN_RUNMODE => 'login', = 158 = POST_LOGIN_RUNMODE => 'okay', = 159 = RENDER_LOGIN => \&my_login_form, = 160 = ); = 161 = = 162 = # define runmodes (pages) that require successful login: = 163 = $self->authen->protected_runmodes( = 164 = 'mustlogin', = 165 = ); = 166 = = 167 = } = 168 = = 169 = sub teardown { = 170 = my $self = shift; = 171 = $self->dbh->disconnect(); # close database connection = 172 = } = 173 = = 174 = sub mustlogin : Runmode { = 175 = my $self = shift; = 176 = my $url = $self->query->url; = 177 = return $self->redirect($url); = 178 = } = 179 = = 180 = sub okay : Runmode { = 181 = my $self = shift; = 182 = = 183 = my $url = $self->query->url; = 184 = # my $user = $self->authen->username; = 185 = my $dest = $self->query->param('destination') || 'index'; = 186 = = 187 = if ($url =~ /^https/) { = 188 = $url =~ s/^https/http/; = 189 = } = 190 = = 191 = return $self->redirect("$url/$dest"); = 192 = } = 193 = = 194 = sub login : Runmode { = 195 = my $self = shift; = 196 = my $url = $self->query->url; = 197 = = 198 = my $user = $self->authen->username; = 199 = if ($user) { = 200 = my $message = "User $user is already logged in!"; = 201 = my $template = $self->load_tmpl('default.html'); = 202 = $template->param(MESSAGE => $message); = 203 = $template->param(MYURL => $url); = 204 = return $template->output; = 205 = } else { = 206 = my $url = $self->query->self_url; = 207 = unless ($url =~ /^https/) { = 208 = $url =~ s/^http/https/; = 209 = return $self->redirect($url); = 210 = } = 211 = return $self->my_login_form; = 212 = } = 213 = } = 214 = = 215 = sub my_login_form { = 216 = my $self = shift; = 217 = my $template = $self->load_tmpl('login_form.html'); = 218 = = 219 = (undef, my $info) = split(/\//, $ENV{'PATH_INFO'}); = 220 = my $url = $self->query->url; = 221 = = 222 = my $destination = $self->query->param('destination'); = 223 = = 224 = unless ($destination) { = 225 = if ($info) { = 226 = $destination = $info; = 227 = } else { = 228 = $destination = "index"; = 229 = } = 230 = } = 231 = = 232 = my $error = $self->authen->login_attempts; = 233 = = 234 = $template->param(MYURL => $url); = 235 = $template->param(ERROR => $error); = 236 = $template->param(DESTINATION => $destination); = 237 = return $template->output; = 238 = } = 239 = = 240 = sub logout : Runmode { = 241 = my $self = shift; = 242 = if ($self->authen->username) { = 243 = $self->authen->logout; = 244 = $self->session->delete; = 245 = } = 246 = return $self->redirect($self->query->url); = 247 = } = 248 = = 249 = sub myerror : ErrorRunmode { = 250 = my $self = shift; = 251 = my $error = shift; = 252 = my $template = $self->load_tmpl("default.html"); = 253 = $template->param(NAME => 'ERROR'); = 254 = $template->param(MESSAGE => $error); = 255 = $template->param(MYURL => $self->query->url); = 256 = return $template->output; = 257 = } = 258 = = 259 = sub AUTOLOAD : Runmode { = 260 = my $self = shift; = 261 = my $rm = shift; = 262 = my $template = $self->load_tmpl("default.html"); = 263 = $template->param(NAME => 'AUTOLOAD'); = 264 = $template->param(MESSAGE => = 265 = "<p>Error: could not find run mode \'$rm\'<br>\n"); = 266 = $template->param(MYURL => $self->query->url); = 267 = return $template->output; = 268 = } = 269 = = 270 = = 271 = 1; = 272 =

Listing 5: WebApp/templates/index.html

(go back)
= 273 = <html> = 274 = <head> = 275 = <META HTTP-EQUIV="Expires" CONTENT="Tue, 04 Dec 1993 21:29:02 +GMT"> = 276 = <title> = 277 = <TMPL_IF NAME> <TMPL_VAR NAME> </TMPL_IF> = 278 = </title> = 279 = </head> = 280 = <body> = 281 = = 282 = <TMPL_IF NAME> = 283 = <p>This is the <TMPL_VAR NAME> page. </p> = 284 = </TMPL_IF> = 285 = = 286 = <p> <hr/> </p> = 287 = = 288 = <TMPL_IF MESSAGE> = 289 = <p> <TMPL_VAR MESSAGE> </p> = 290 = </TMPL_IF> = 291 = = 292 = <TMPL_IF USER> = 293 = <p> User: <TMPL_VAR USER> is logged in. </p> = 294 = <p> Click <a href="<TMPL_VAR MYURL>/logout">here</a> to logo +ut. </p> = 295 = <TMPL_ELSE> = 296 = <p> Click <a href="<TMPL_VAR MYURL>/mustlogin">here</a> to l +ogin. </p> = 297 = </TMPL_IF> = 298 = = 299 = <p> <hr/> </p> = 300 = <p><a href="<TMPL_VAR MYURL>/public">public</a></p> = 301 = <p><a href="<TMPL_VAR MYURL>/public2">public2</a></p> = 302 = <p><a href="<TMPL_VAR MYURL>/private">private</a></p> = 303 = <p><a href="<TMPL_VAR MYURL>/private2">private2</a></p> = 304 = <p> <hr/> </p> = 305 = </body> = 306 = </html> = 307 =

Listing 6: WebApp/templates/default.html

(go back)
= 308 = <html> = 309 = <head> = 310 = <META HTTP-EQUIV="Expires" CONTENT="Tue, 04 Dec 1993 21:29:02 +GMT"> = 311 = <title> = 312 = <TMPL_IF NAME> <TMPL_VAR NAME> </TMPL_IF> = 313 = </title> = 314 = </head> = 315 = <body> = 316 = <TMPL_IF NAME> = 317 = <p>This is the <TMPL_VAR NAME> page. </p> = 318 = </TMPL_IF> = 319 = = 320 = <TMPL_IF MESSAGE> = 321 = <p> <TMPL_VAR MESSAGE> </p> = 322 = </TMPL_IF> = 323 = = 324 = <TMPL_IF MYURL> = 325 = <p> <a href="<TMPL_VAR MYURL>">Back</a> </p> = 326 = </TMPL_IF> = 327 = <hr/> = 328 = </body> = 329 = </html> = 330 =

Listing 7: WebApp/templates/login_form.html

(go back)
= 331 = <html> = 332 = <head> = 333 = <title>Log In</title> = 334 = </head> = 335 = <body onLoad="document.loginform.authen_username.focus();"> = 336 = <h2>Please login:</h2> = 337 = <TMPL_IF ERROR> = 338 = <p style="color:red">ERROR - Please try again.</p> = 339 = </TMPL_IF> = 340 = <p> </p> = 341 = <form name="loginform" method=POST action="<TMPL_VAR MYURL>/mu +stlogin" > = 342 = <table> = 343 = <tr> = 344 = <td align="right">User name:</td> = 345 = <td><input type="text" name="authen_username" size=20 /></ +td> = 346 = </tr> = 347 = <tr> = 348 = <td align="right">Password:</td> = 349 = <td><input type="password" name="authen_password" size=20 +/></td> = 350 = </tr> = 351 = <tr> = 352 = <td> &nbsp; </td> = 353 = <td><input type="submit" name="submit" value="Submit" /></ +td> = 354 = </tr> = 355 = </table> = 356 = <input type="hidden" name="destination" value="<TMPL_VAR DESTI +NATION>" /> = 357 = </form> = 358 = <hr/> = 359 = </body> = 360 = </html> = 361 =