Link to home
Start Free TrialLog in
Avatar of zorlac
zorlac

asked on

How to maintain data in CGI Perl

Hi,
How can I maintain data inputed by user (i.e. username) throughout the session? I'm a java programmer and I'm just starting to program CGI Perl and I just wanted to know if there's something like session object in Perl that I could keep my data. Thanks
Avatar of ozo
ozo
Flag of United States of America image

<input type=hidden name=username value= zorlac>
This is a basic, but fundamental issue when designing for CGI, because as you've already noticed, by default it's a stateless model. Each request-answer is self contained and doesn't intrinsically reference previous states. This can be quite frustrating if your're an application developer because applications maintain their own state as the user follows the application workflow. In Perl, PHP and other CGI environments it's up to the programmer to maintain this state - and to manage the workflow.

So, having said that rather pedantic preamble: to brass tacks. There are a good many ways this can be done.

Let's get the easy one over with:
http://search.cpan.org/~lds/CGI.pm-3.05/CGI.pm#CREATING_A_SELF-REFERENCING_URL_THAT_PRESERVES_STATE_INFORMATION:

use strict; # you always use strict, don't you?
use CGI; # use the Perl module called CGI.pm. Nowadays, this is standard with your perl installation, if not, it can be found at www.cpan.org

open STATE_FILE, "my_state_file.txt" || die ("Couldn't open STATE FILE: $!"); # open the file or complain
my $cgi = CGI->new(STATE_FILE); # create a new CGI object and read in the previous state's variables (the user's input)
close STATE_FILE;


# ... and then do some stuff

# and just before exiting your script:
open STATE_OUT, ">my_state_file.txt" || die ("Couldn't write STATE FILE: $!");
$cgi->save(STATE_OUT);
close STATE_OUT;

# ---- end 1 --------

Now this assumes you want to let CGI.pm handle all this for you. Personally, I don't use this method, because A) i like more control, B) i don't like the idea of saving state to a file, especially in the context of a highly-trafficked site that could be handling hundreds of transactions more-or-less simultaneously and C) i'm stubborn and like doing things myself!

So, a few other options:

As you no doubt know, workflow is usually initiated by a call to your script (let's call it "my_script.pl"), either through a form's "action" parameter or directly via a hyperlink. Parameters are passed to the script either via POST (from your form), or via GET (embedded in the URL). Either way, somewhere near the start of your script, you want to read these params in so that you can decide where in the workflow you are and what you're going to do next.

State can be controlled using any number of schemes. I often like to do something simple like using a parameter called "cmd" or "c" (to be somewhat more cryptic) and pass the "state" in the workflow. For example in a typical application you might:

1. display a "registration" form which is submitted
2. validate the from and send it back if it is incorrect
3. create a new record and show the user the login page
4. authenticate the user and toss them back the login page if they don't authenticate
5. show them the "goodies" once they've authenticated.

So here we have some distinct states in the workflow which can be summarized thusly:
show_registration
register_user
login

The first call to this script would be to the registration page so our "cmd" variable would be "show_registration"
You could call the script via "my_script.pl?cmd=show_registration". When the script encounters this parameter it knows where in the workflow it's supposed to be and thus displays the registration form.

Hidden in the registration form would be the "cmd" parameter, presumably in a hidden field like this:
<form name="regForm" action="my_script.pl" method="POST"><input type="hidden" name="cmd" value="register_user">...form stuff</form>

So now when the user completes the form and hits "Submit" the cmd parameter is "register_user" and the perl scipt knows that it has to validate the form, and if it's valid, create the new user record and spit out the login page.

So on the Perl side, you create some kind of control structure that switches based on the cmd parameter:

use CGI; # why? because it's a darned convenient way of reading user input from forms.
# Alternatively you can require "cgi-lib". Google cgi-lib.pl for more info.
my $cgi = new CGI;
my %params = %{$cgi->Vars}; # read all the params from your input form into a hash in the form of $hash{name} = 'value';

# simplest method:
if ($params{'cmd'} eq 'show_registration') {
  # display the registration form.....
}
elsif ($params{'cmd'} eq 'register_user') {
  # validate the user and then spit out the login page if valid
}
#.... etc
else {
  # some default action, like show the main menu or whatever
}

There are plenty of ways you can do this, one favourite being to create a hash of function references.

But this might not be getting to your question. Or well, it's only part of it. What about keeping track of the user's input? Like a username?

The key here, if you want to avoid the CGI->save() route is to keep passing the parameters back to the script every time you call the script. It's not as cumbersome as you might think, especially if your script is form-driven (as opposed to URL driven).

Let's say the registration process has 3 pages. On the first page, the user creates a name and password, on the second and third pages she then enters additional information. How do you maintain the name and password so that the script knows that it's that particular user who is submitting pages 2 and 3 of the form? Simple: using Perl, re-embed the username and password into the form (or use cookies, but that's another story for another day).

Let's say that the user has created a username and password. We're on page 2 of the form, and we're creating the form using Perl:

# display page 2 of the form
print qq { <html><head><title>page 2</title></head><body> }; # hey, we want well-formed html here, right?
# Note the qq { } format - great for embedding HTML because you don't have to worry about escaping your " and '

# start the form, embed the "cmd" parameter
print qq { <form name="page2" action="my_script.pl" method="POST"><input type="hidden" name="cmd" value="regstration_2"> };

# now, embed the user's previous input (ie: the username and password). They go back into hidden fields.
print qq { <input type="hidden" name="username" value="$params{'username'}"> }; # the qq { } will automagically expand the variable
print qq { <input type="hidden" name="password" value="$params{'password'}"> };
# if there is more data you want to pass, then add it here using the same format as above
# and then add the "new" form fields, for example:
print qq { <p>Company name: <input type="text" name="company"></p> };
# etc... you get the picture
print qq { </form></body></html> };

Pretty simple, huh? Now, when the user hits the SUBMIT button, the old data (username and password) are sent to the script along with the new data.

Hope this helps!

Tom Auger

PS: one last note. Sometimes, to make your design more flexible you want to use an external file to define your forms, rather than embedding the whole darn thing in Perl, which makes it nearly impossible for other non-programmer designers to modify the forms. In that case you read in the external html file, spit out the appropriate MIME-headers and then the file contents using a loop and "print". But how do you embed user data in there?

use strict;
use CGI;
my $cgi = new CGI;
my %params = %{$cgi->Vars};

# print headers
print $cgi->header();

# now, read in the external "template" file and spit it out - we'll be re-using this, so let's create a function for it.
output_template("my_template_html_file.html", \%params);

# ...
sub output_template {
  my $template_file = shift;
  my $params_ref = shift;

  # read the file
  open TEMPL, $template_file || die ("Couldn't access the template file : $!");
  while (<TEMPL>) {
    # now, for each line of the template file, look for "special" tags that we define as looking like this: <*my_user_input_parameter>
    s/<\*(.+?)>/$params_ref->{$1}/g;
    print $_;
  }
  close TEMPL;
}

And our template form might look like this:
<html><head><title>templated form</title></head>
<form name="myForm" action="my_script.pl" method="POST">
<p><input type="text" name="username" value="<*username>"></p>
</form>

<p>Yes, your name is: <*username></p>
<p>And your password is: <*password></p>
</body></html>

So as the form is being read in by the output_template function in your Perl script, it's looking for those "special" tags that we've defined of the form <*paramter_name>. When it encounters one of them, it will automatically substitute in the actual value from the user's previous input (the stuff that's stored in the %params hash). If it doesn't match, a nice side benefit is that it substitutes with "" so either way the "special" tag never appears to people sniffing around your source code.

Whew! I need a beer.
ASKER CERTIFIED SOLUTION
Avatar of tomaugerdotcom
tomaugerdotcom
Flag of Canada image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks for the grade of "B". I wonder what else I could have done to deserve an "A" - perhaps write a Master's Thesis?