Chapter 4. Cookies
A cookie is just a
name=value
pair, much like the named parameters used
in the CGI query string and discussed in CGI
Programming. 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 inside 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 when the browser and server are communicating via 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 preferred 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. Cookies now have their own standard (RFC 2109) and are accepted by all major browsers.
Creating Cookies
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. This 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 reference
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 each space into
%20
, as the HTTP 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.
Retrieving Cookies
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 reference:
$c = cookie( -name => 'specials', -value => ['oatmeal', 'chocolate chip','alfalfa']);
Or you can save and restore entire hashes:
$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 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, you can pass an
-expires
parameter containing the cookie’s shelf
life to the cookie
function. 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
.
A Sample Cookie Program
Example 4-1 is a CGI script
called configure.cgi
that generates pages such as
Figure 4-1. When you call this
script’s URL, you are 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 remembers all of these values and builds a page based
on them.
00 #!/usr/bin/perl 01 02 use CGI qw(:standard :html3); 03 04 # Some constants to use in our form. 05 @colors = qw/aqua black blue fuchsia gray green lime maroon navy olive purple red silver teal white yellow/; 06 @sizes = ("<default>", 1..7); 07 08 # Recover the "preferences" cookie. 09 %preferences = cookie('preferences'); 10 11 # If the user wants to change the name or background color, they can 12 foreach ('text', 'background', 'name', 'size') { 13 $preferences{$_} = param($_) || $preferences{$_}; 14 } 15 16 # Set some defaults 17 $preferences{background} = $preferences{background} || 'silver'; 18 $preferences{text} = $preferences{text} || 'black'; 19 20 # Refresh the cookie so that it doesn't expire. 21 $the_cookie = cookie( -name => 'preferences', 22 -value => \%preferences, 23 -path => '/', 24 -expires => '+30d'); 25 print header(-cookie => $the_cookie); 26 27 # Adjust the title to incorporate the user's name, if provided. 28 $title = $preferences{name} ? "Welcome back, $preferences{name}!" : "Customizable Page"; 29 30 # Create the HTML page, controlling the background color and font size. 31 # 32 print start_html( -title => $title, 33 -bgcolor => $preferences{background}, 34 -text => $preferences{text}); 35 36 print basefont({SIZE=>$preferences{size}}) if $preferences{size} > 0; 37 38 print h1($title),<<END; 39 You can change the appearance of this page by submitting 40 the fill-out form below. If you return to this page any time 41 within 30 days, your preferences will be restored. 42 END 43 ; 44 # Create the form. 45 print hr, 46 start_form, 47 48 "Your first name: ", 49 textfield( -name => 'name', 50 -default => $preferences{name}, 51 -size => 30), br, 52 table( 53 TR( 54 td("Preferred"), 55 td("Page color:"), 56 td(popup_menu( -name => 'background', 57 -values => \@colors, 58 -default => $preferences{background}) 59 ) 60 ), 61 TR( 62 td(''), 63 td("Text color:"), 64 td(popup_menu( -name => 'text', 65 -values => \@colors, 66 -default => $preferences{text}) 67 ) 68 ), 69 TR( 70 td(''), 71 td("Font size:"), 72 td(popup_menu( -name => 'size', 73 -values => \@sizes, 74 -default => $preferences{size}) 75 ) 76 ) 77 ), 78 submit(-label => 'Set preferences'), 79 end_form, 80 hr; 81 82 print a({HREF => "/"}, 'Go to the home page');
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 HTML 3.2 standard, and they’re based on the original colors used by the IBM VGA graphics display.
Line 9 is where we recover the user’s previous preferences, if
any. We use the cookie
method to fetch a cookie
named “preferences”, and store its value in a like-named hash.
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
hash, replacing the original
values.
Line 17 and 18 set the text and background 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 expiration 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 available 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. To make it
personalizable, we base the page title on the user’s name. If it’s
set, the title and level 1 header both become “Welcome back
<name>!” Otherwise, the title becomes an impersonal
“Customizable page.” Line 32 calls the start_html
method to create the top part of the HTML page. It sets the title, the
background color and the text color based on the values in the
%preferences
array. Line 36 sets the text size by
calling the basefont
method. This simply generates
a <BASEFONT>
HTML tag with an appropriate SIZE
attribute.
Lines 38 and up generate the content of the page. There’s a
brief introduction to the page, followed by the fill-out form used to
change the settings. All the HTML is generated using CGI.pm
“shortcuts,” in which tags are generated by like-named method calls.
For example, the hr
method generates the HTML tag
<HR>
. 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 elements were
slightly different widths, everything was crooked. To fix this
problem, 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
<TABLE>
and </TABLE>
tags. Within this are a series of TR
methods, each
of which generates a <TR>
tag. (In order to
avoid conflict with Perl’s built-in tr///
operator,
this is one instance where CGI.pm uses uppercase rather than lowercase
shortcut names.) Within each TR
call, in turn,
there are several td
calls that generate the
<TD>
(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
preprocesses 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 <BASEFONT>
and
<BODY>
tags based on the cookie preferences.
Now, just configure the server to pass all HTML documents through this
script, and you’re set.
In the next article, Doug MacEachern and I introduce mod_perl, a Perl interpreter embedded inside the Apache web server.
Get Web, Graphics & Perl/Tk Programming now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.