Apache The Definitive Guide, 3rd EditionApache: The Definitive GuideSearch this book

16.2. Telling Apache About the Script

Since we have two different techniques here, we have two Config files: .../conf/httpd1.conf and .../conf/httpd2.conf . The script go takes the argument 1 or 2.

You need to do either of the following:

16.2.1. Script in cgi-bin

Use ScriptAlias in your host's Config file, pointing to a safe location outside your web space. This makes for better security because the Bad Guys cannot read your scripts and analyze them for holes. "Security by obscurity" is not a sound policy on its own, but it does no harm when added to more vigorous precautions.

To steer incoming demands for the script to the right place (.../cgi-bin), we need to edit our ... /site.cgi/conf/httpd1.conf file so it looks something like this:

User webuser
Group webgroup
ServerName www.butterthlies.com

#for scripts in ../cgi-bin
ScriptAlias /cgi-bin /usr/www/APACHE3/cgi-bin
DirectoryIndex /cgi-bin/script_html

You would probably want to proceed in this way, that is, putting the script in the cgi-bin directory (which is not in /usr/www/APACHE3/site.cgi/htdocs), if you were offering a web site to the outside world and wanted to maximize your security. Run Apache to use this script with the following:

./go 1

You would access this script by browsing to http://www.butterthlies.com/cgi-bin/mycgi.cgi.

16.2.2. Script in DocumentRoot

The other method is to put scripts in among the HTML files. You should only do this if you trust the authors of the site to write safe scripts (or not write them at all) since security is much reduced. Generally speaking, it is safer to use a separate directory for scripts, as explained previously. First, it means that people writing HTML can't accidentally or deliberately cause security breaches by including executable code in the web tree. Second, it makes life harder for the Bad Guys: often it is necessary to allow fairly wide access to the nonexecutable part of the tree, but more careful control can be exercised on the CGI directories.

We would not suggest you do this unless you absolutely have to. But regardless of these good intentions, we put mycgi.cgi in.../site.cgi/htdocs. The Config file, ... /site.cgi/conf/httpd2.conf, is now:

User webuser
Group webgroup
ServerName www.butterthlies.com
DocumentRoot /usr/www/APACHE3/site.cgi/htdocs
AddHandler cgi-script cgi
Options  

ExecCGI

Use Addhandler to set a handler type of cgi-script with the extension .cgi. This means that any document Apache comes across with the extension.cgi will be taken to be an executable script.You put the CGI scripts, called <name>.cgi in your document root. You also need to have Options ExecCGI . To run this one, type the following:

./go 2

You would access this script by browsing to http://www.butterthlies.com/cgi-bin/mycgi.cgi.

To experiment, we have a simple test script, mycgi.cgi, in two locations: .../cgi-bin to test the first method and.../site.cgi/htdocs to test the second. When it works, we would write the script properly in C or Perl or whatever.

Figure

The script mycgi.cgi looks like this:

#!/bin/sh
echo "Content-Type: text/plain"
echo
echo "Have a nice day"

Figure

Under Win32, providing you want to run your script under COMMAND.COM and call it mycgi.bat, the script can be a little simpler than the Unix version — it doesn't need the line that specifies the shell:

@echo off
echo "Content-Type: text/plain"
echo.
echo "Have a nice day"

Figure

The @echo off command turns off command-line echoing, which would otherwise completely destroy the output of the batch file. The slightly weird-looking echo. gives a blank line (a plain echo without a dot prints ECHO is off).

Figure

If you are running a more exotic shell, like bash or perl, you need the "shebang" line at the top of the script to invoke it. These must be the very first characters in the file:

#!shell path
...

16.2.3. Perl

You can download Perl for free from http://www.perl.org. Read the README and INSTALL files and do what they say. Once it is installed on a Unix system, you have an online manual. perldoc perldoc explains how the manual system works. perldoc -f print, for example, explains how the function print works; perldoc -q print finds "print" in the Perl FAQ.

A simple Perl script looks like this:

#! /usr/local/bin/perl -wT
use strict;

print "Hello world\n";

The first line, the "shebang" line, loads the Perl interpreter (which might also be in /usr/bin/perl) with the -wT flag, which invokes warnings and checks incoming data for "taint." Tainted data could have come from Bad Guys and contain malicious program in disguise. -T makes sure you have always processed everything that comes from "outside" before you use it in any potentially dangerous functions. For a fuller explanation of a complicated subject, see Programming Perl by Larry Wall, Jon Orwant, and Tom Christiansen (O'Reilly, 2000). There isn't any input here, so -T is not necessary, but it's a good habit to get into.

The second line loads the strict pragma: it imposes a discipline on your code that is essential if you are to write scripts for the Web. The third line prints "Hello world" to the screen.

Having written this, saved it as hello.pl and made it executable with chmod +x hello.pl, you can run it by typing ./hello.pl.

Whenever you write a new script or alter an old one, you should always run it from the command line first to detect syntax errors. This applies even if it will normally be run by Apache. For instance, take the trailing " off the last line of hello.pl, and run it again:

Can't find string terminator '"' anywhere before EOF at ./hello.pl line 4

16.2.4. Databases

Many serious web sites will need a database in back. In the authors' experience, an excellent choice is MySQL, freeware made in Scandinavia by intelligent and civilized people. Download it from http://www.mysql.com. It uses a variant of the more-or-less standard SQL query language. You will need a book on SQL: Understanding SQL by Martin Gruber (Sybex, 1990) tells you more than you need to know, although the SQL syntax described is sometimes a little different from MySQL's. Another option is SQL in a Nutshell by Kevin Kline (O'Reilly, 2000). MySQL is fast, reliable, and so easy to use that a lot of the time you can forget it is there. You link to MySQL from your scripts through the DBI module. Download it from CPAN (http://www.cpan.org/) if it doesn't come with Perl. You will need some documentation on DBI — try http://www.symbolstone.org/technology/perl/DBI/doc/faq.html. There is also an O'Reilly book on DBI, Programming the Perl DBI by Alligator Descartes and Tim Bunce. In practice, you don't need to know very much about DBI because you only need to access it in five different ways. See the lines marked 'A', 'B', 'C', 'D', and 'E' in script as follows:

'A' to open a database
'B' to execute a single command - which could equally well have been typed at the 
keyboard as a MySQL command line.
'C' to retrieve, display, process fields from a set of database records. A very nice 
thing about MySQL is that you can use the 'select *' command, which will make all 
the fields available via the $ref->{'<fieldname>'} mechanism.
'D' Free up a search handle 
'E' Disconnect from a database

If you forget the last two, it can appear not to matter since the database disconnect will be automatic when the Perl script terminates. However, if you then move to mod_perl (discussed in Chapter 17), it will matter a lot since you will then accumulate large numbers of memory-consuming handles. And, if you have very new versions of MySQL and DBI, you may find that the transaction is automatically rolled back if you exit without terminating the query handle.

This previous script assumes that there is a database called people. Before you can get MySQL to work, you have to set up this database and its permissions by running:

mysql mysql < load_database

where load_database is the script .../cgi-bin/load_database:

create database people;

INSERT INTO db VALUES 
('localhost','people','webserv','Y','Y','Y','Y','N','N','N','N','N','N');

INSERT INTO user VALUES 
('localhost','webserv','','Y','Y','Y','Y','N','N','N','N','N','N','N','N','N','N');
INSERT INTO user VALUES ('<IP address>
','webserv','','Y','Y','Y','Y','N','N','N','N','N','N','N','N','N','N');

You then have to restart with mysqladmin reload to get the changes to take effect.

Newer versions of MySQL may support the Grant command, which makes things easier.

You can now run the next script, which will create and populate the table people:

mysql people < load_people

The script is .../cgi-bin/load_people:

# MySQL dump 5.13
#
# Host: localhost    Database: people
#--------------------------------------------------------
# Server version 3.22.22

#
# Table structure for table 'people'
#
CREATE TABLE people (
  xname varchar(20),
  sname varchar(20)
);

#
# Dumping data for table 'people'
#

INSERT INTO people VALUES ('Jane','Smith');
INSERT INTO people VALUES ('Anne','Smith');
INSERT INTO people VALUES ('Anne-Lise','Horobin');
INSERT INTO people VALUES ('Sally','Jones');
INSERT INTO people VALUES ('Anne-Marie','Kowalski');

It will be found in .../cgi-bin.

Another nice thing about MySQL is that you can reverse the process by:

mysqldump people > load_people

This turns a database into a text file that you can read, archive, and upload onto other sites, and this is how the previous script was created. Moreover, you can edit self contained lumps out of it, so that if you wanted to copy a table alone or the table and its contents to another database, you would just lift the commands from the dump file.

We now come to the Perl script that exercises this database. To begin with, we ignore Apache. It is .../cgi-bin/script:

#! /usr/local/bin/perl -wT
use strict;
use DBI( );
my ($mesg,$dbm,$query,$xname,$sname,$sth,$rows,$ref);

$sname="Anne Jane";
$xname="Beauregard";

# Note A above: open a database
$dbm=DBI->connect("DBI:mysql:database=people;host=localhost",'webuser')
     or die "didn't connect to people";

#insert some more data just to show we can
$query=qq(insert into people (xname,sname) values ('$xname',$sname'));
#Note B above: execute a command
$dbm->do($query);

# get it back
$xname="Anne";
$query=qq(select xname, sname from people where xname like "%$xname%");
#Note C above: 
$sth=$dbm->prepare($query) or die "failed to prepare $query: $!";

# $! is the Perl variable for the current system error message
$sth->execute;
$rows=$sth->rows;
print qq(There are $rows people with names matching '$xname'\n);
while ($ref=$sth->fetchrow_hashref)
    {
    print qq($ref->{'xname'} $ref->{'sname'}\n);
    }
#D: free the search handle
$sth->finish;
#E: close the database connection
$dbm->disconnect;

Stylists may complain that the $dbm->prepare($query) lines, together with some of the quoting issues, can be neatly sidestepped by code like this:

$surname="O'Reilly";
$forename="Tim";
...
$dbm->do('insert into people(xname,sname) values (?,?)',{},$forename,$surname);

The effect is that DBI fills in the ?s with the values of the $forename, $surname variables. However, building a $query variable has the advantage that you can print it to the screen to make sure all the bits are in the right place — and you can copy it by hand to the MySQL interface to make sure it works — before you unleash the line:

$sth=$dbm->prepare($query)

The reason for doing this is that a badly formed database query can make DBI or MySQL hang. You'll spend a long time staring at a blank screen and be no wiser.

For the moment, we ignore Apache. When you run script by typing ./script, it prints:

There are 4 people with names matching 'Anne'
Anne Smith
Anne-Lise Horobin
Anne Jane Beauregard
Anne-Marie Kowalski

Each time you run this, you add another Beauregard, so the count goes up.

MySQL provides a direct interface from the keyboard, by typing (in this case) mysql people. This lets you try out the queries you will write in your scripts. You should try out the two $query s in the previous script before running it.

16.2.5. HTML

The script we just wrote prints to the screen. In real life we want it to print to the visitor's screen via her browser. Apache gets it to her, but to get the proper effect, we need to send our data wrapped in HTML codes. HTML is not difficult, but you will need a thorough book on it,[55] because there are a large number of things you can do, and if you make even the smallest mistake, the results can be surprising as browsers often ignore badly formed HTML. All browsers will put up with some harmless common mistakes, like forgetting to put a closing </body></html> at the end of a page. Strictly speaking, attributes inside HTML tags should be in quotes, thus:

[55]Chuck Musciano and Bill Kennedy's HTML &XHTML: The Definitive Guide (O'Reilly, 2002) is a thorough treatment. You might also find that a lightweight handbook like Chris Russell's HTML in Easy Steps (Computer Step, 1998) is also useful.

<A target="MAIN"...>
<Font color="red"...>

However, the browsers do not all behave in the same way. MSIE, for instance, will tolerate the absence of a closing </form> or </table> tags, but Netscape will not. The result is that pages will, strangely, work for some visitors and not for others. Another trap is that when you use Apache's ability to pass extra data in a link when CGI has been enabled by ScriptAlias:

<A HREF="/my_script/data1/data2">

(which results in my_script being run and /data1/data2 appearing in the environment variable PATH_INFO), one browser will tolerate spaces in the data, and the other one will not. The moral is that you should thoroughly test your site, using at least the two main browsers (MSIE and Netscape) and possibly some others. You can also use an HTML syntax checker like WebLint, which has many gateways, e.g., http://www.ews.uiuc.edu/cgi-bin/weblint, or Dr. HTML at http://www2.imagiware.com/RxHTML/.

16.2.6. Running a Script via Apache

This time we will arrange for Apache to run the script. Let us adapt the previous script to print a formatted list of people matching the name "Anne." This version is called .../cgi-bin/script_html.

#! /usr/local/bin/perl -wT
use strict;
use DBI( );

my ($ref,$mesg,$dbm,$query,$xname,$sname,$sth,$rows);

#print HTTP header
print "content-type: text/html\n\n";

# open a database
$dbm=DBI->connect("DBI:mysql:database=people;host=localhost",'webserv')
    or die "didn't connect to people";

# get it back
$xname="Anne";
$query=qq(select xname, sname from people where xname like "%$xname%");
$sth=$dbm->prepare($query) or die "failed to prepare $query: $!";

# $! is the Perl variable for the current system error message
$sth->execute;
$rows=$sth->rows;

#print HTML header
print qq(<HTML><HEAD><TITLE>People's names</TITLE></HEAD><BODY>
<table border=1 width=70%><caption><h3>The $rows People called '$xname'</h3></caption>
<tr><align left><th>First name</th><th>Last name</th></tr>);
while ($ref=$sth->fetchrow_hashref)
    {
    print qq(<tr align = right><td>$ref->{'xname'}</td><td> $ref->{'sname'}</td></tr>);
    }
print "</table></BODY></HTML>";
$sth->finish;
# close the database connection
$dbm->disconnect;

16.2.7. Quote Marks

The variable that contains the database query is the $query string. Within that we have the problem of quotes. Perl likes double quotes if it is to interpolate a $ or @ value; MySQL likes quotes of some sort around a text variable. If we wanted to search for the person whose first name is in the Perl variable $xname, we could use the query string:

$query="select * from people where xname='$xname'";

This will work and has the advantage that you can test it by typing exactly the same string on the MySQL command line. It has the disadvantages that while you can, mostly, orchestrate pairs of '' and " ", it is possible to run out of combinations. It has the worse disadvantage that if we allow clients to type a name into their browser that gets loaded into $xname, the Bad Guys are free to enter a name larded with quotes of their own, which could do undesirable things to your system by allowing them to add extra SQL to your supposedly innocuous query.

Perl allows you to open up the possibilities by using the qq( ) construct, which has the effect of double external quotes:

$query=qq(select * from people where xname="$xname");

We can then go on to the following:

$sth=$dbm->prepare($query) || die $dbm->errstr;
$sth->execute($query);

But this doesn't solve the problem of attackers planting malicious SQL in $xname.

A better method still is to use MySQL's placeholder mechanism. (See perldoc DBI.) We construct the query string with a hole marked by ? for the name variable, then supply it when the query is executed. This has the advantage that no quotes are needed in the query string at all, and the contents of $xname completely bypass the SQL parsing, which means that extra SQL cannot be added via that route at all. (However, note that it is good practice always to vet all user input before doing anything with it.) Furthermore, database access runs much faster since preparing the query only has to happen once (and query optimization is often also performed at this point, which can be an expensive operation). This is particularly important if you have a busy web site doing lookups on different things:

$query=qq(select * from people where xname=?);
$sth=$dbm->prepare($query) || die $dbm->errstr;

When you want the database lookup to happen, you write:

$sth->execute($query,$xname);

This has an excellent impact on speed if you are doing the database accesses in a loop.

In the script script: first we print the HTTP header — more about this will follow. Then we print the HTML header, together with the caption of the table. Each line of the table is printed separately as we search the database, using the DBI function fetchrow_hashref to load the variable $ref. Finally, we close the table (easily forgotten, but things can go horribly wrong if you don't) and close the HTML.

#! /usr/local/bin/perl -wT
use strict;
use DBI( );

my ($ref,$mesg,$dbm,$query,$xname,$sname,$sth,$rows);

$xname="Anne Jane";
$sname="Beauregard";

# open a database
$dbm=DBI->connect("DBI:mysql:database=people;host=localhost",'webserv')
    or die "didn't connect to DB people";

#insert some more data just to show we can
# demonstrate qq( )
$query=qq(insert into people (xname,sname) values ('$xname','$sname'));
$dbm->do($query);

# get it back
$xname="Anne";
#demonstrate DBI placeholder
$query=qq(select xname, sname from people where xname like ?);
$sth=$dbm->prepare($query) or die "failed to prepare $query: $!";
# $! is the Perl variable for the current system error message

#Now fill in the placeholder
$sth->execute($query,$xname);
$rows=$sth->rows;
print qq(There are $rows people with names matching '$xname'\n);
while ($ref=$sth->fetchrow_hashref)
    {
    print qq($ref->{'xname'} $ref->{'sname'}\n);
    }
$sth->finish;
# close the database connection
$dbm->disconnect;

This script produces a reasonable looking page. Once you get it working, development is much easier. You can edit it, save it, refresh from the browser, and see the new version straight away.

Use ./go 1 and browse to http://www.butterthlies.com to see a table of girls called "Anne." This works because in the Config file we declared this script as the DirectoryIndex.

In this way we don't need to provide any fixed HTML at all.

16.2.8. HTTP Header

One of the most crucial elements of a script is also hard to see: the HTTP header that goes ahead of everything else and tells the browser what is coming. If it isn't right, nothing happens at the far end.

A CGI script produces headers and a body. Everything up to the first blank line (strictly speaking, CRLF CRLF, but Apache will tolerate LF LF and convert it to the correct form before sending to the browser) is header, and everything else is body. The lines of the header are separated by LF or CRLF.

The CGI module (if you are using it) and Apache will send all the necessary headers except the one you need to control. This is normally:

print "Content-Type: text/html\n\n";

If you don't want to send HTML — but ordinary text — as if to your own screen, use the following:

print "Content-Type: text/plain\n\n";

Notice the second \n (C and Perl for newline), which terminates the headers (there can be more than one; each on its own line), which is always essential to make the HTTP header work. If you find yourself looking at a blank browser screen, suspect the HTTP header.

If you want to force your visitor's browser to go to another URL, include the following line:

print "Location: http://URL\n\n"

CGIs can emit almost any legal HTTP header (note that although "Location" is an HTTP header, using it causes Apache to return a redirect response code as well as the location specified — this is a special case for redirects). A complete list of HTTP headers can be found in section 14 of RFC2616 (the HTTP 1.1 specification), http://www.ietf.org/rfc/rfc2616.txt.

16.2.9. Getting Data from the Client

On many sites in real life, we need to ask the visitor what he wants, get the information back to the server, and then do something with it. This, after all, is the main mechanism of e-commerce. HTML provides one standard method for getting data from the client: the Form. If we use the HTML Method='POST' in the form specification, the data the user types into the fields of the form is available to our script by reading stdin.

In POST-based Perl CGI scripts, this data can be read into a variable by setting it equal to <>:

my ($data);
$data=<>;

We can then rummage about in $data to extract the values type in by the user.

In real life, you would probably use the CGI module, downloaded from CPAN (http://cpan.org), to handle the interface between your script and data from the form. It is easier and much more secure than doing it yourself, but we ignore it here because we want to illustrate the basic principles of what is happening.

We will add some code to the script to ask questions. One question will ask the reader to click if they want to see a printout of everyone in the database. The other will let them enter a name to replace "Anne" as the search criterion listed earlier.

It makes sense to use the same script to create the page that asks for input and then to handle that input once it arrives. The trick is to test the input channels for data at the top of the script. If there is none, it asks questions; if there is some, it gives answers.

16.2.9.1. Data from a link

If your Apache Config file invokes CGI processing with the directive ScriptAlias, you can construct links in your HTML that have extra data passed with them as if they were directory names passed in the Environment variable PATH_INFO. For instance:

...
<A HREF="/cgi-bin/script2_html/whole_database">Click here to see whole database</A>
...

When the user clicks on this link she invokes script2_html and makes available to it the Environment variable PATH_INFO, containing the string /whole_database. We can test this in our Perl script with this:

if($ENV{'PATH_INFO'} eq '/whole_database')
{
#do something
}

Our script can then make a decision about what to do next on the basis of this information. The same mechanism is available with the HTML FORM ACTION attribute. We might set up a form in our HTML with the command:

<FORM METHOD='POST' ACTION="/cgi-bin/script2_html/receipts">

As previously, /receipts will turn up in PATH_INFO, and your script knows which form sent the data and can go to the appropriate subroutine to deal with it.

What happens inside Apache is that the URI — /cgi-bin/script2_html/receipts — is parsed from right to left, looking for a filename, which does not have to be a CGI script. The material to the right of the filename is passed in PATH_INFO.

16.2.9.2. CGI.pm

The Perl module called CGI.pm does everything we discuss and more. Many professionals use it, and we are often asked why we don't show it here. The answer is that to get started, you need to know what is going on under the hood and that is what we cover here. In fact, I tried to start with CGI.pm and found it completely baffling. It wasn't until I abandoned it and got my hands in the cogs that I understood how the interaction between the client's form and the server's script worked. When you understand that, you might well choose to close the hood in CGI.pm. But until then, it won't hurt to get to grips with the underlying process.

16.2.9.3. Questions and answers

Since the same script puts up a form that asks questions and also retrieves the answers to those questions, we need to be able to tell in which phase of the operation we are. We do that by testing $data to find out whether it is full or empty. If it is full, we find that all the data typed into the fields of the form by the user are there, with the fields separated by &. For instance, if the user had typed "Anne" into the first-name box and "Smith" into the surname box, this string would arrive:

xname=Anne&sname=Smith 

or, if the browser is being very correct:

xname=Anne;sname=Smith 

We have to dissect it to answer the customer's question, but this can be a bit puzzling. Not only is everything crumpled together, various characters are encoded. For instance, if the user had typed "&" as part of his response, e.g., "Smith&Jones", it would appear as "Smith%26Jones". You will have noticed that "26" is the ASCII code in hexadecimal for "&". This is called URL encoding and is documented in the HTTP RFC. "Space" comes across as "+" or possibly "%20". For the moment we ignore this problem. Later on, when you are writing real applications, you would probably use the "unescape" function from CGI.pm to translate these characters.

The strategy for dealing with this stuff is to:

  1. Split on either "&" or ";" to get the fields

  2. Split on "=" to separate the field name and content

  3. (Ultimately, when you get around to using it) use CGI::unescape($content), the content to get rid of URL encoding

See the first few lines of the following subroutine get_name( ). This is the script .../cgi-bin/script2_html, which asks questions and gets the answers. There are commented out debugging lines scattered through the script, such as:

#print "in get_name: ARGS: @args, DATA: $data<BR>";

Put these in to see what is happening, then turn them off when things work. You may like to leave them in to help with debugging problems later on.

Another point of style: many published Perl programs use $dbh for the database handle; we use $dbm:

#! /usr/local/bin/perl -wT
use strict;
use DBI( );
use CGI;
use CGI::Carp qw(fatalsToBrowser);

my ($data,@args);

$data=<>;

if($data)
    {
    &get_name($data);
    }
elsif($ENV{'PATH_INFO'} eq "/whole_database")
    {
    $data="xname=%&sname=%";
    &get_name( );
    }
else
    {
    &ask_question;
    }
print "</BODY></HTML>";


sub ask_question
{
&print_header("ask_question");

print qq(<A HREF="/cgi-bin/script2_html/whole_database">
Click here to see the whole database</A>

<BR><FORM METHOD='POST' ACTION='/cgi-bin/script2_html/name'>
Enter a first name <INPUT TYPE='TEXT' NAME='xname' SIZE=20><BR>
and or a second name <INPUT TYPE='TEXT' NAME='sname' SIZE=20><BR>
<INPUT TYPE=SUBMIT VALUE='ENTER'>);

}

sub print_header
{
print qq(content-type: text/html\n\n
<HTML><HEAD><TITLE>$_[0]</TITLE></HEAD><BODY>);
}

sub get_name
{
my ($t,@val,$ref,
    $mesg,$dbm,$query,$xname,$sname,$sth,$rows);

&print_header("get_name");
#print "in get_name: ARGS: @args, DATA: $data<BR>";
    $xname="%";
    $sname="%";
@args=split(/&/,$data);

foreach $t (@args)
    {
    @val=split(/=/,$t);
    if($val[0] eq "xname")
        {
        $xname=$val[1] if($val[1]);
        }
    elsif($val[0] eq "sname")
        {
        $sname=$val[1] if($val[1]);
        }
    }

# open a database
$dbm=DBI->connect("DBI:mysql:database=people;host=localhost",'webserv')
    or die "didn't connect to people";

# get it back
$query=qq(select xname, sname from people where xname like ? 
and sname like ?);
$sth=$dbm->prepare($query) or die "failed to prepare $query: $!";
#print "$xname, $sname: $query<BR>";

# $! is the Perl variable for the current system error message

$sth->execute($xname,$sname) or die "failed to execute $dbm->errstr( )<BR>";
$rows=$sth->rows;
#print "$rows: $rows $query<BR>";

if($sname eq "%" && $xname eq "%")
    {
    print qq(<table border=1 width=70%><caption><h3>The Whole Database (3)</h3></
caption>);
    }
else
    {
    print qq(<table border=1 width=70%><caption><h3>The $rows People called $xname 
$sname</h3></caption>);
    }
    
print qq(<tr><align left><th>First name</th><th>Last name</th></tr>);
while ($ref=$sth->fetchrow_hashref)
    {
    print qq(<tr align right><td>$ref->{'xname'}</td><td> $ref->{'sname'}</td></tr>);
    }
print "</table></BODY></HTML>";
$sth->finish;
# close the database connection
$dbm->disconnect;
}

The Config file is ...site.cgi/httpd3.conf.

User webuser
Group webgroup
ServerName www.butterthlies.com
DocumentRoot /usr/www/APACHE3/APACHE3/site.cgi/htdocs

# for scripts in .../cgi-bin
/cgi-bin /usr/www/APACHE3/APACHE3/cgi-bin
DirectoryIndex /cgi-bin/script2_html

Kill Apache and start it again with ./go 3.

The previous script handles getting data to and from the user and to and from the database. It encapsulates the essentials of an active web site — whatever language it is written in. The main missing element is email — see the following section.

16.2.10. Environment Variables

Every request from a browser brings a raft of information with it to Apache, which reappears as environment variables. It can be very useful to have a subroutine like this:

sub print_env
    {
    foreach my $e (keys %ENV)
        {
        print "$e=$ENV{$e}\n";
        }
    }

If you call it at the top of a web page, you see something like this on your browser screen:

SERVER_SOFTWARE = Apache/1.3.9 (Unix) mod_perl/1.22
GATEWAY_INTERFACE = CGI/1.1
DOCUMENT_ROOT = /usr/www/APACHE3/MedicPlanet/site.medic/htdocs
REMOTE_ADDR = 192.168.123.1
SERVER_PROTOCOL = HTTP/1.1
SERVER_SIGNATURE = 
REQUEST_METHOD = GET
QUERY_STRING = 
HTTP_USER_AGENT = Mozilla/4.0 (compatible; MSIE 4.01; Windows 95)
PATH = /sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:
/usr/X11R6/bin:/root/bin
HTTP_ACCEPT = image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, 
application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, */*
HTTP_CONNECTION = Keep-Alive
REMOTE_PORT = 1104
SERVER_ADDR = 192.168.123.5
HTTP_ACCEPT_LANGUAGE = en-gb
SCRIPT_NAME = 
HTTP_ACCEPT_ENCODING = gzip, deflate
SCRIPT_FILENAME = /usr/www/APACHE3/MedicPlanet/cgi-bin/MP_home
SERVER_NAME = www.Medic-Planet-here.com
PATH_INFO = /
REQUEST_URI = /
HTTP_COOKIE = Apache=192.168.123.1.1811957344309436; Medic-Planet=8335562231
SERVER_PORT = 80
HTTP_HOST = www.medic-planet-here.com
PATH_TRANSLATED = /usr/www/APACHE3/MedicPlanet/cgi-bin/MP_home/
SERVER_ADMIN = [no address given

All of these environment variables are available to your scripts via $ENV. For instance, the value of $ENV{'GATEWAY_INTERFACE'} is 'CGI/1.1' — as you can see earlier.

Environment variables can also be used to control some aspects of the behavior of Apache. Note that because these are just variables, nothing checks that you have spelled them correctly, so be very careful when using them.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.