This page is under construction.
In the last installment of this column I promised to talk about MiniSvr, a central part of the CGI::* modules and an easy way to main tain state within a CGI script. Unfortu nately I've made several changes to my system in the last few weeks (upgrading to Perl 5.003 and Apache 1.1b4), and MiniSvr broke. At press time, I still haven't figured out what's gone wrong. So today I'm going to talk about a sweeter sub ject, cookies.
What's a cookie? The folks at Netscape came up with the idea for Navigator 1.1. It's just a name=value pair, much like the named parameters used in the CGI query string. When a Web server or CGI script wants to save some state information, it creates a cookie or two and sends them to the browser inside the HTTP header. The browser keeps track of all the cookies sent to it by a particular server and stores them in an on-disk database so that the cookies persist even when the browser is closed and reopened later. The next time the browser connects to a Web site, it searches its database for all cookies that belong to that server and transmits them back to the server within the HTTP header. Cookies can be permanent or set to expire after a number of hours or days. They can be made site-wide, so that the cookie is available to every URL on your site, or restricted to a partial URL path. You can also set a flag in the cookie so that it's only transmitted over the Internet when the browser and server are communicating by a secure protocol such as SSL. You can even create promiscuous cookies that are sent to every server in a particular Internet domain. The idea is simple but powerful. If a CGI script needs to save a small amount of state information, such as the user's pre ferred background color, it can be stored directly in a cookie. If lots of information needs to be stored, you can keep the information in a database on the server's side and use the cookie to record a session key or user ID. Other browsers have begun to adopt cookies (notably Microsoft in its Internet Explorer), and cookies are on their way to becoming a part of the HTTP standard.
So how do you create a cookie? If you use the CGI.pm library it's a piece of cake: 0 #!/usr/bin/perl 1 2 use CGI qw(:standard); 3 4 $cookie1 = cookie(-name => 'regular', 5 -value => 'chocolate chip'); 6 $cookie2 = cookie(-name => 'high fiber', 7 -value => 'oatmeal raisin'); 8 print header(-cookie => [$cookie1, $cookie2]); Line 2 loads the CGI library and imports the :standard set of function calls using a syntax that's new in library versions 2.21 and higher. This syntax allows you to call all of the CGI object's methods without explicitly creating a CGI instance - a default CGI object is created for you behind the scenes. Lines 4 through 7 create two new cookies using the CGI cookie() method. The last step is to incorporate the cookies into the document's HTTP header. We do this in line 8 by printing out the results of the header() method, passing it the -cookie parameter along with an array refer ence containing the two cookies. When we run this script from the command line, the result is: Set-cookie: regular=chocolate%20chip Set-cookie: high%20fiber=oatmeal%20raisin Content-type: text/html
As you can see, CGI.pm translates spaces into %20's, as the Netscape cookie specification prohibits whitespace and certain other characters, such as the semicolon. (It also places an upper limit of a few kilobytes on the size of a cookie, so don't try to store the text of Hamlet in one.) When the browser sees these two cookies it squirrels them away and returns them to your script the next time it needs a document from your server. To retrieve the value of a cookie sent to you by the browser, use cookie() without a -value parameter:
0 #!/usr/bin/perl 1 2 use CGI qw(:standard); 3 4 $regular = cookie('regular'); 5 $high_fiber = cookie('high fiber'); 6 7 print header(-type => 'text/plain'), 8 "The regular cookie is $regular.\n", 9 "The high fiber cookie is $high_fiber.";
In this example, lines 4 and 5 retrieve the two cookies by name. Lines 7 through 9 print out an HTTP header (containing no cookie this time), and two lines of text. The output of this script, when viewed in a browser, would be
The regular cookie is chocolate chip. The high fiber cookie is oatmeal raisin.The cookie() method is fairly flexible. You can save entire arrays as cookies by giving the -value parameter an array ref erence:
$c = cookie(-name => 'specials', -value => ['oatmeal', 'chocolate chip','alfalfa']); Or you can save and restore whole associative arrays: $c = cookie(-name => 'prices', -value => { 'oatmeal' => '$0.50', 'chocolate_chip' => '$1.25', 'alfalfa' => 'free'});
Later you can recover the two cookies this way: @specials = cookie('specials'); %prices = cookie('prices');
By default, browsers will remember cookies that only until they exit, and will only send the cookie out to scripts with a URL path that's similar to the script that generated it. If you want them to remember the cookie for a longer period of time, pass an -expires parameter to cookie() containing the cookie's shelf life. To change the URL path over which the cookie is valid, pass its value in -path:
$c = cookie(-name => 'regular',
-value => 'oatmeal raisin',
-path => '/cgi-bin/bakery',
-expires => '+3d');
This cookie will expire in three days' time (`+3d'). Other
cookie() parameters allow you to adjust the domain names and
URL paths that trigger the browser to send a cookie, and to turn
on cookie secure mode. The -path parameter shown here tells
the browser to send the cookie to every program in
/cgi-bin/bakery.
The next page shows a CGI script called configure.cgi. When
you call this script's URL you're presented with the fill-out
form shown above. You can change the page's background
color, the text size and color, and even customize it with your
name. The next time you visit this page (even if you've closed
the browser and come back to the page weeks later), it remem
bers all of these values and builds a page based on them.
This script recognizes four CGI parameters used to change the
configuration:
· background: Set the background color.
· text: Set the text color.
· size: Set the size to the indicated value (1-7).
· name: Set the username.
Usually these parameters are sent to the script via the fill out
form that it generates, but you could set them from within a
URL this way:
/cgi-bin/configure.pl?background=silver&text=blue&name=Stein
Let's walk through the code. Line 2 imports the CGI library,
bringing in both the standard method calls and a number of
methods that generate HTML3-specific tags. Next we define a
set of background colors and sizes. The choice of colors may
seem capricious, but it's not. These are the background colors
defined by the newly-released HTML3.2 standard, and they're
based on the original colors used by the IBM VGA graphics dis
play.
Line 9 is where we recover the user's previous preferences, if
any. We use the cookie() method to fetch a cookie named "pref
erences," and store its value in a like-named associative array.
In lines 12 through 14, we fetch the CGI parameters named
text, background, name, and size. If any of them are set, it
indicates that the user wants to change the corresponding value
saved in the browser's cookie. We store
changed parameters in the %preferences
associative array, replacing the original
values.
Line 17 and 18 set the text and back
ground colors to reasonable defaults if
they can't be found in either the cookie
or the CGI script parameters.
Lines 21 through 25 generate the page's
HTTP header. First we use the cookie()
method to create the cookie containing
the user's preferences. We set the expira
tion date for the cookie for 30 days in the
future so that the cookie will be removed
from the browser's database if the user
doesn't return to this page within that
time. We also set the optional -path
parameter to /. This makes the cookie
valid over our entire site so that it's avail
able to every URL the browser fetches.
Although we don't take advantage of this
yet, it's useful if we later decide that
these preferences should have a site-
wide effect. Lastly we emit the HTTP
header with the -cookie parameter set.
In lines 30 to 36 we begin the HTML
page. True to the intent of making it per
sonalizable, we base the page title on the
user's name. If it's set, the title and level
1 header both become "Welcome back
. As shown in the first column in
this series, we start the fill-out form with
a call to start_form(), create the various
form elements with calls to textfield(),
popup_menu(), and submit(), and close
the form with end_form().
When I first wrote this script, the popup
menus and popup menus in the form
didn't line up well. Because all the ele
ments were slightly different widths,
everything was crooked. To fix this prob
lem, I used the common trick of placing
the form elements inside an invisible
HTML3 table. Assigning each element
to its own cell forces the fields to line up.
You can see how I did this in lines 52
through 77, where I define a table using
a set of CGI.pm shortcuts. An outer call
to table() generates the surrounding
and
tags. Within this
are a series of TR() methods, each of
which generates a tag. (In order to
avoid conflict with Perl's built-in tr///
operator, this is one instance where
CGI.pm uses uppercase rather than low
ercase shortcut names.) Within each
TR() call, in turn, there are several td()
calls that generate the ("table data")
cells of the HTML table.
Fortunately my text editor auto-indents
nicely, making it easy to see the HTML
structure.
On a real site, of course, you'd want the
user's preferences to affect all pages, not
just one. This isn't a major undertaking;
many modern Web servers now allow
you to designate a script that prepro
cesses all files of a certain type. You can
create a variation on the script shown
here that takes an HTML document and
inserts the appropriate