We know how to write a CGI script that says 'Hello!':
#!/usr/bin/perl
print qq{Content-type: text/html\n\n};
print qq{<html><head><title>Title</title<>/head><body>}; 
print qq{<h1>Hello!</h1>};
print qq{</body></html>}; 
We abstract a few actions:
#!/usr/bin/perl

sub HtmlTop {
  print qq{Content-type: text/html\n\n};
  print qq{<html><head><title>Title</title<>/head><body>};
}

sub HtmlBot {
  print qq{</body></html>};
}

# and here comes the actual program 

&HtmlTop;
print qq{<h1>Hello</h1>};
&HtmlBot; 
We have looked at %ENV:
&HtmlTop;
foreach $key (keys %ENV) {
  print $key, " --> ", $ENV{$key}, "\n";
} 
&HtmlBot; 
or with each as explained in the lab.

The essence of CGI is to use the values transmitted through these variables, do some processing, and send back a document that describes the result of the computation.

A few of the environment variables that will be of interest to us:

The two request methods that will be described today are: Remember that when a script is called through a URL the request method is GET, as described in lecture 2. This is the simplest request method, and we will work with it first, but before that we are going to see how we can make the output of our Hello! script be more dynamic.

Maybe we can do that randomly.

rand generates random numbers.

But you need srand first.

Although the distribution is OK the sequence is deterministic.

The general use is:

rand (EXPR) 
which returns a random fractional number between 0 and the value to which EXPR evaluates to.

Generate 10 random numbers between 0 and 50:

for ($i=0; $i<10; $i++) {
  print rand(50); 
} 
Every time you run it: same numbers.

srand($ARGV[0]);
for ($i=0; $i<10; $i++) {
  print rand(50); 
} 
is more flexible, you cant start it wherever you want it.

Numbers are real (floating point).

If you need integers:

for ($i=0; $i<10; $i++) {
  print int(rand(50)); 
} 
which truncates them.

Now we want to be surprised, we want the sequence to be out of our reach. We will use the system time.

$seconds = time;
$date = localtime; 
print $seconds, " seconds since Jan 1970, ",
      "today's date and time is: $date\n";       
time returns a number of seconds, and localtime returns an actual date in a scalar context (such as the one dictated by $date).

We can use this to generate numbers:

srand(time); 
for ($i=0; $i < 10; $i++) {
  print rand(50); 
} 
Even less predictable:
srand (time % $$); 
print rand(50); 
where % is the modulus operator and $$ is the process id.

So now we are ready to print random quotations.

@sayings = ("saying 1", "saying 2", "saying 3"); 
srand(time % $$); 
print int(rand($#saying + 1)); 
Everytime we run this program we get a new one, hard to predict which.

Now we return to the world of scripts:

$time = localtime;
&htmltop;
print $time;
&HtmlBot;
print the date on the server.

The output varies but we can't control it.

Fortunately we remember the %ENV experiment.

Let's redo part of it. Create myscript:

#!/usr/bin/perl
&HtmlTop;
$qs = $ENV{'QUERY_STRING'};
print "The query string is: ", $qs;
&HtmlBot;
Now call this program like this:
http://tucotuco:19800/cgi-bin/myscript
and then
http://tucotuco:19800/cgi-bin/myscript?test 
$ENV{'QUERY_STRING'} binds to the part that comes after ? (question mark). That's the convention. Now that we know it we can use this as a sort of $ARGV[0] parameter:
#!/usr/bin/perl
&HtmlTop;
$qs = $ENV{'QUERY_STRING'}; 
if ($qs eq "time") { 
  print "The date is: ", localtime; 
} elsif ($qs eq "hostname") {
  print "You are calling from: ",
        $ENV{'REMOTE_HOST'}; 
} elsif ($qs eq "quotation") {
  @sayings = ("saying 1", "saying 2", "saying 3"); 
  srand (time % $$); 
  print $sayings[int(rand($#sayings + 1))]; 
} elsif ($qs eq "ipnumber") {
  print "Your ip number is ", 
        $ENV{'REMOTE_ADDR'}; 
} else {
  print "Hello!"; 
}
&HtmlBot; 
So now we can query the script from the 'command line' (that is, the URL).

The following functionality is defined:

http://server:port/cgi-bin/myscript?time
http://server:port/cgi-bin/myscript?hostname
http://server:port/cgi-bin/myscript?quotation
http://server:port/cgi-bin/myscript?ipnumber
http://server:port/cgi-bin/myscript
Fine, but if the user wants to issue a new command?

Let's build a menu.

First we abstract away another piece of code:

sub process_query { # note: $qs is global 
  if ($qs eq "time") { 
    print "The date is: ", localtime; 
  } elsif ($qs eq "hostname") { 
    print "You are calling from: ",
          $ENV{'REMOTE_HOST'}; 
  } elsif ($qs eq "quotation") {
    @sayings = ("saying 1", "saying 2", "saying 3"); 
    srand (time % $$); 
    print $sayings[int(rand($#sayings + 1))]; 
  } elsif ($qs eq "ipnumber") {
    print "Your ip number is ", 
          $ENV{'REMOTE_ADDR'}; 
  } else { print "Hello!"; }   
} 
So our program becomes:
&HtmlTop;
&process_query;
&HtmlBot; 
The menu will provide the set of options as links/calls.
sub menu {
  $myurl = "http://" .
           "tucotuco.cs.indiana.edu:19800" . 
           "/cgi-bin/myscript"; 
  print qq {
    <hr> <ul>
    <li> <a href="$myurl?time">Time</a> 
    <li> <a href="$myurl?hostname">Hostname</a>
    <li> <a href="$myurl?quotation">Quotation</a>
    <li> <a href="$myurl?ipnumber">IP Number</a>
    </ul> 
  } 
}
and the script becomes:
&HtmlTop;
$qs = $ENV{'QUERY_STRING'};
&process_query;
&menu;
&HtmlBot; 
We could be even more context-sensitive, and disable a link to the call that produce the information already listed (so you can't call twice in a row with the same query).

Here, then, is the new menu:

sub menu
  local ($param) = @_; 
  %param = (
      "time"      => "Time", 
      "hostname"  => "Hostname", 
      "quotation" => "Quotation", 
      "ipnumber"  => "IP Number"
  ); 
  print "<hr><ul>"; 
  foreach $key ("time", 
                "hostname", 
                "quotation", 
                "ipnumber"
    # this way we control the order
                ) { 
    if ($key eq $param) { 
      print "<li>$param{$key}"; 
    } else {
      print qq{<a href="$myurl?$key">$param{$key}</a>}; 
    } 
  }
  print "</ul>"; 
}  
The rest of the program stays the same, except you now have to pass $qs to menu. With this new menu the user can never request the same thing over and over again unless (s)he reloads the page. So the output is a bit more context-sensitive.

&HtmlTop;
$qs = $ENV{'QUERY_STRING'};
&process_query;
&menu($qs);
&HtmlBot; 
Now let's try to use what we know and build a calculator, as we did in the lab, see if we can.

Arguments to the program come after a ? at the end of the URL, but they have to be one full string, without blanks.

So let's make this convention: we'll use underscores instead of blanks.

First we'll make a simple expression evaluator, but a really simple one: we'll implement only operators (addition, subtraction, etc) that take two arguments.

Commands will look like this:

http://server:port?add_5_34
http://server:port?sub_34_2
http://server:port?mul_2_2
and so forth.

The program is easy to write:

#!/usr/bin/perl
&HtmlTop;
$qs = $ENV{'QUERY_STRING'}; 
($fun, $arg1, $arg2) = split(/_/, $qs); 
if ($fun =~ /^add/i) {
  print "$arg1 + $arg2 = ", $arg1 + $arg2; 
} elsif ($fun =~ /^sub/i) {
  print "$arg1 - $arg2 = ", $arg1 - $arg2; 
} else {
  print "We only add and sub. Kinko's does more."; 
} 
So this is a simple expression evaluator, now let's try to build a calculator.

#!/usr/bin/perl
&HtmlTop;
$qs = $ENV{'QUERY_STRING'}; 
($com, $arg) = split(/_/, $qs); 
if ($com =~ /^add/i) { 
  $acc += $arg;
  print "\$acc is now $acc."; 
} elsif ($com =~ /^sub/i) { 
  $acc -= $arg; 
  print "\$acc is now $acc."
} else {
  print "\$acc stays $acc."; 
} 
&HtmlBot; 
Now the trouble with this calculator is that it is completely through with us once it prints the results. We need to do the business of sending it back the accumulator (but there isn't any accumulator per se we should rather say we refer to the value of the accumulator, instead of the accumulator itself) so that we can keep working with it over a longer period of time.

But as you probably notice this is different from the program in lab: we didn't need to care about the accumulator in that case. How then can we build a CGI-based calculator that more closely mimics the behaviour that we originally implemented in the lab?

There are only two ways:

The trouble with the first option is the fact that (since there is no direct connection, as in the case of a telnet session) we do not know if the calculator user will call again or when. If (s)he calls we need to retrieve her state/register and perform arithmetic on it as indicated by the new query. But that involves a lot of work to make sure we identify the right user, and also involves issues of disk space management, since if we keep the state/register on the server we may want to set an expiration date on the data itself (we never know when the user may call again but we don't want to keep the data forever on our server either, there must be a compromise).

So that involves a lot of bookkeeping.

But we don't want to be involved in this now, we want to be done with these examples in just one lecture, this lecture.

So we choose to send the data to the user. But we will do that in a transparent manner such that the user won't know (unless the user wants to know). We won't be concerned with security issues, but we will see later that there are interesting and important aspects that need to be considered here regarding security that we need to be aware of.

In this particular case it really doesn't matter, though.

Let's now look at a simple form:

<form method="POST" action="$myurl">
Operator: <select name="fun">
            <option selected value="fun"> Click me!
            <option value="add"> Add
            <option value="sub"> Sub 
          </select> <p>
Argument: <input type="text" name="arg"> <hr> 
<input type="submit" value="Calculate">
</form>

It displays as follows:


Operator:

Argument:



Or almost. (I actually have put a reset button instead of a submit button in the form to avoid confusing error messages if someobody clicks on it right here. You can view the source if you want).

The method="POST" part works in direct relationship with the action="$myurl" element and $myurl needs to be a valid URL.

The URL is called when the user pushes the form's submit and it is treated as a program; data is passed to it, more precisely tha values of the form's fields, in this case fun and arg are passed to it. We make a subroutine that produces this form, it is the initial form, and call it &show_form:

sub show_form { print qq {
    <form method="POST" action="$myurl">
  Operator: <select name="fun">
            <option selected value="fun"> Click me!
            <option value="add"> Add
            <option value="sub"> Sub 
            </select> <p>
  Argument: <input type="text" name="arg"> <hr> 
            <input type="submit" value="Calculate">
   </form>
  }
} 

Now our new program looks like this:

#!/usr/local/bin/perl &HtmlTop; if ($ENV{'REQUEST_METHOD'} eq 'GET') { &show_form; } else { read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); print $buffer; } &HtmlBot;
The program works as follows: A CGI script communicates with the server through the STDIN and STDOUT.

We read the data from STDIN into a buffer, and the size of the data, so that we know how much we read from the input stream is in the ENV variable indexed by 'CONTENT_LENGTH'.

We then print the buffer.

The buffer returns something of this kind:

fun=add&arg=34
so we need to break this string into usable components.

We create a subroutine to process the input/incoming data.

sub read_data {
  read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
  @pairs = split(/&/, $buffer); 
  foreach $pair (@pairs) {
    ($name, $value) = split(/=/, $pair); 
    $FORM{$name} = $value; 
  } 
} 
and one to perform the computations:
sub process_data {
  if ($FORM{fun} eq "add") {
    $FORM{acc} += $FORM{arg}; 
  } elsif ($FORM{fun} eq "sub") {
    $FORM{acc} -= $FORM{arg}; 
  } else {

  } 
} 
and we're almost done.

We only need to more one last change: to make sure the accumulator gets passed on, from one invocation to the next.

We therefore add a hidden field to the form and we obtain the following final program:

#!/usr/bin/perl

&HtmlTop;
if ($ENV{'REQUEST_METHOD'} eq 'GET') { &show_form; }
else {
  &read_data;
  &process_data;
  &show_form($FORM{acc}); 
}
&HtmlBot;

sub HtmlTop {
  print qq{Content-type: text/html\n\n<html>
    <head><title>Calculator</title></head><body>
  } 
} 

sub HtmlBot {
  print qq{</body></html>}; 
} 

sub show_form {
  local ($acc) = @_; $acc = 0 if (! $acc); 
  $myurl = # your own URl, the demo program uses
  "http://www.best.indiana.edu/cgi-bin/a348/calculator"; 
  print qq{ 
    The accumulator is currently: $acc 
    <form method="post" action="$myurl">
    Operator: <select name="fun">
    <option selected value="none"> Click me!
    <option value="add"> Add
    <option value="sub"> Sub 
    </select> <p>
    Argument: <input type="text" name="arg"> <p>
    <input type="hidden" name="acc" value="$FORM{acc}">
    <input type="submit" value="Compute">
    </form>
  } 
} 

sub read_data {
  read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
  @pairs = split(/&/, $buffer); 
  foreach $pair (@pairs) {
    ($name, $value) = split(/=/, $pair); 
    $FORM{$name} = $value; 
  } 
} 

sub process_data {
  if ($FORM{fun} eq "add") {
    $FORM{acc} += $FORM{arg}; 
  } elsif ($FORM{fun} eq "sub") {
    $FORM{acc} -= $FORM{arg}; 
  } else {

  } 
} 
A few things to note: