Difference between revisions of "CGI POST"

From "A B C"
Jump to navigation Jump to search
m
(No difference)

Revision as of 14:51, 16 September 2012

Using forms in Perl


The contents of this page has recently been imported from an older version of this Wiki. This page may contain outdated information, information that is irrelevant for this Wiki, information that needs to be differently structured, outdated syntax, and/or broken links. Use with caution!


Summary ...



 

HTML forms

HTML forms provide a convenient way to send more complex information to a server. Essentially all forms do is:

  1. Define the URL the data is to be sent to
  2. Define the method that is to be used (usually "POST")
  3. Provide some way to capture user data (text areas, radio buttons, check-boxes etc.)
  4. Provide some mechanism to send the data to the server (the "submit" button).

Forms are written into HTML source. Here is a minimal example. Save this code and open it in a browser. Then type some text into the form and click submit. The form gets sent to the printEnv.pl program you have written previously.

<html>
<head><title>My first form</title></head>
<body>

<form method="POST" action="http://127.0.0.1/test/printEnv.pl">
Data: <input type="text" name="data_1">
<input type="submit" value="Submit ...">
</form>

</body>
</html>


Some new environment variable should appear - for example the CONTENT_LENGTH should contain the number of bytes in your POSTed data, and HTTP_REFERER should show the name of your HTML file. But where is the data itself? Read on.

Notes on the code

  • Here is a link to an overview of form elements. Try some.
  • The file is a normal HTML file and can contain any other HTML markup as well. It can also contain any number of forms, or submit buttons: the data that gets POSTed is the one from the form that contains the submit button that was clicked.


The data that was posted is made available to the script in STDIN. But there is a catch: the server does not send EOF. Instead, a script programmer has to evaluate the number of bytes the data contains, and read exactly that number of bytes from STDIN. This is a bit awkward and much easier to achieve with a a perl module called CGI.

(If you want to look at sample code that processes raw CGI STDIN input, see here.)

Summary of form-based input

  1. A Web page contains a form that captures instructions;
  2. a user fills in the form and submits it;
  3. the form is sent to the Web server. It usually contains a POST request to an application;
  4. the Web server invokes the application and passes the data through STDIN;
  5. the program runs, retrieves the data from STDIN, parses out the fields and their values sends its output to STDOUT;
  6. the Web server accepts this data and sends it to the requesting browser.


Using the perl CGI module

Modules are packaged functions or programs that provide functionality to perl scripts. They wide variety of well written, useful code that can be readily imported and used is one of the strongest points in favour of Perl. Typically, modules are submitted by programmers to CPAN, a large community archive and are available under open-source licenses.

The CGI module is one such example, however it should already be installed in a standard perl distribution. To check this, type

$ perl -MCGI
<ctrl D>

If CGI is' installed, nothing will happen. If it is not, you will get a slew of error statements. In that case, let me know , get the module from CPAN or try typing...

$ sudo perl -MCPAN -e "install CGI"

... which runs the CPAN module for you and attempts to find and install the module.


Your dataflow can capture input from an HTML form and pass that to a CGI script for processing. But it is often preferrable to keep everything in one place and to let the script respond appropriately, depending on whether it is being executed without form input (when called as an HTML document) or with input (when called to respond to a POST request). This is sound practice, the easiest way to ensure that the form and its processing script do not go out of synchrony. It is easy to implement this this, using a so called hidden form element. Consider this example:

#!/usr/bin/perl
# file: contextForm.pl
# Boris Steipe, 2008

use strict;
use warnings;
use CGI  qw/:standard/;

    my $Phi = 1.6180339887498948482;
    my $defN = 22;
    my $defD = 17;  # 11/8.5 is US letter paper size 
    my ($N, $D);
    my $Error = "";

    print "Content-type: text/html\n\n";
    
    print "<html>\n";
    print "<head><title>Rational phi</title></head>\n";
    print "<body>\n";

    print "Try to find a good rational approximation to phi, the \"";
    print "<a href=\"http://en.wikipedia.org/wiki/Golden_ratio\">golden ratio</a>\".<br>(Try to get better than 0.1%).\n";

    print"<form method=\"post\" action=\"", $ENV{"SCRIPT_NAME"}, "\">\n";
    print "<input type=\"hidden\" name=\"fromForm\" value=\"1\">\n";
    print "<p>\n";

    if ( param("fromForm") ) { # ...  is TRUE: we have been invoked by the POST request
                               # ... therefore the parameters "numerator" and "denominator" should exist as well.
        $N = sanitize( param("numerator") );
        $D = sanitize( param("denominator") );
        print "Last input for numerator was: \"$N\"<br>\n";
        print "Last input for denominator was: \"$D\"<br>\n";
    }
    
    if (! $N) {                   # if $N is FALSE
        if (! param("lastN")) {   # ... and no valid value has been passed through the form
            $N = $defN;           # ... use the default
        }
        else {
            $N = param("lastN");  # ... else use the last valid input
        }
    }                             # ... else a new value for $N has been accepted

    if (! $D) {
        if (! param("lastD")) {
            $D = $defD;
        }
        else {
            $D = param("lastD");
        }
    }

    print "<input type=\"hidden\" name=\"lastN\" value=\"$N\">\n";  # Remember this as last good value
    print "<input type=\"hidden\" name=\"lastD\" value=\"$D\">\n";
    
    print "Current approximation: $N/$D = ", $N/$D, " (error of ", ($Phi-($N/$D)) / ($Phi/100) ,"%)\n";
    print "<p>&nbsp;<p>\n";
    
    print "Change numerator: <input type=\"text\" name=\"numerator\" value=\"$N\"><br>\n";
    print "Change denominator: <input type=\"text\" name=\"denominator\" value=\"$D\">\n";

    print "<p>\n";
    print "<input type=\"submit\" value=\"Update and submit ...\">\n";
    print "</form>\n";

    print "</body>\n";
    print "</html>\n";

#=============================================

sub sanitize {
        my $in = $_[0];
    if (! defined($in) ) { return (0); }    
    $in =~ s/\D//g;    # remove all non-numeric characters
    # truncate to ten digits
    if (length($in) > 10) { return ( substr($in,0,10) ); }
    else { return ($in); }
}

Notes on the code

  • If the script is called with a GET request, "fromForm" is FALSE.
  • If the script is called with a POST request from the form it has created, "fromForm" is TRUE and we evaluate the numerator and denominator.
  • The subroutine handles only integers. Floating point number sanitation would require not to delete the first period (decimal point) in an input.
  • The sanitizer does its work silently. In "real" code you should always let a user (or yourself) know that the input was not according to rules and what the rules are.
  • <p>&nbsp;<p> creates two consecutive paragraph marks. If there would be nothing, or a simple blank, the paragraphs should be collapsed. But &nbsp; does not count as white-space!

Exercise

Add a subroutine that tests efficiently whether one of the previous input numbers is a Fibonacci number, i.e. one from the sequence (1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ...)



   

Further reading and resources