by Shelley Powers
Chapter 4 "Advanced Page Output," contained some discussion of generating HTML content, and Chapter 6 "Using Dynamic Pages," extended this description with a detailed description of Web page redirection, the CGI environment variables, client pull, and server push. This chapter discusses additional aspects of using Perl to develop interactive and dynamic Web applications.
No matter how dynamic or interesting a page is, no one page can please all people or provide information that interests all people at all times. That old adage says you can please some of the people all of the time or all of the people some of the time, but you can never please all of the people all of the time. Application developers have always been acutely aware of this adage, particularly when they try to get the users of their application to buy off on a design or a deliverable.
Recent technology permits most of us to change our environments, at least on the computer. We have control of features as simple as what screen saver we use and what background we display; we can also control more complex features, such as what macros we install and what features we enable in the applications that we use.
In Web application development, the ideal approach is to present a list of options to a particular Web page reader and allow that reader to choose what content he or she wants to see and the way that the content is presented. Both Netscape and Microsoft implement some version of this approach with their personalized home pages.
You also can implement this approach by providing a shopping bin of components and allowing your users to select components from the bin. After a user makes his choices, the information is stored under his name or alias. When the user accesses the main document, such as INDEX.CGI, the application accesses the file that stores this information and basically builds the user's page, based on his preferences and including only the components that he picked. For a new user who does not have a previously defined preference, the application displays a generic Web page that includes an option allowing that person to define his own unique Web page. Creating user-specific pages usually implies two types of processing: one to allow the user to define what he wants on his page, and the second to actually create the page.
The first technique to implement for the shopping-bin approach is to build a Web page that lists the options. This page must be accessible from your main Web site page. You also need to create the content bins. To do so, examine your Web site and determine what its major components are. If you are selling a product, you could organize the components in a catalog layout, or you could organize by department. If your site provides general information-such as the current weather, exciting events that are occurring that week, or major product announcements-you could classify each type of information as a major component.
For the example in this chapter, the major components are:
Normally, all these components would be available directly in the main Web page or as links from the main page. In the shopping-bin approach, part of the information will be listed in the user-specific Web page, and additional information will be listed as links.
After you determine the content of each section, the next step in creating a shopping bin is creating a form that asks for and processes the Web page reader's user name and password. Chapter 8, "Understanding Basic User Authentication," covers user authorization, and this form is covered in that chapter. After the user name and password have been processed, a second form opens, allowing the user to pick the options that he or she wants to see.
Figure 7.1 displays a form that allows the user to choose among the components listed earlier in this section. This form is generated by a CGI application that creates the HTML. The application also includes the new user name as part of a hidden field. The user name is sent along with the other form data. Listing 7.1 shows an example of this form.
Listing 7.1 HTML That Allows Web Readers to Specify Preferences (binchoose.cgi)
#!/usr/local/bin/perl # # binchoose.cgi # # Application will generate an HTML document # that contains the user name as a hidden # field. One method of implementing persistence across # HTML documents. # # Application will also list content choices # # add location of CGI.pm BEGIN { push(@INC,'/opt2/web/guide/unix/book-bin'); } use CGI; $query = new CGI; # # if existing parameters, get value for user_name if ($query->param()) { $user_name = $query->param('user_name'); } # # print header and heading information print $query->header; print $query->start_html('Choices'); print $query->h1('Choices'); print "<hr>"; print $query->h3('Choose the contents you want.'); # # start form print $query->start_form(-method=>'POST', -action=>'http://unix.yasd.com/book-bin/process_request.cgi'); # # add hidden field containing user name print $query->hidden(-name=>'user_name',-value=>$user_name); # # add checkboxes for choices print $query->checkbox(-name=>'Company',-label=>'Company Announcements'); print "<br>"; print $query->checkbox(-name=>'Products',-label=>'Product Announcements'); print "<br>"; print $query->checkbox(-name=>'ProductX',-label=>'ProductX Announcements'); print "<br>"; print $query->checkbox(-name=>'ProductY',-label=>'ProductY Announcements'); print "<p>"; print $query->submit; print $query->end_form; # # finish document print "<hr>"; print $query->end_html;
After the form is submitted, the application uses the user name and password to create an entry in the password file for the Web page reader. Chapter 8 "Understanding Basic User Authentication," goes into detail on this process, so it is not covered here. The application uses the rest of the contents to add an entry to a file that contains the user name.
The program that processes the contents adds an entry to a file called USERBIN.INP. Following is the format of the entry that will be added:
username::option1:option2:option3: optionn
The user name is followed by two colons (::), which in turn are followed by a string that contains the options that the reader chose, with the options separated by single colons (:).
After the entry has been created in the USERBIN.INP file, the choices are processed. The resulting Web page displays the Web page reader's choices and a link to the site, as shown in figure 7.2.
The application uses the Perl 5.0 library CGI.pm to process the form after the Web page reader submits it. Listing 7.2 shows the code for this application.
Listing 7.2 CGI to Process the Reader's Choices for a Dynamic Page (process_request.cgi)
#!/usr/local/bin/perl # # process_request.cgi # # Application will access user name and # check for it in password file. # If name exists, reader will get a duplicate # name response and a link back to try again. # # If name does not exist, name and choices are # added to file. # BEGIN { push(@INC,'/opt2/web/guide/unix/book-bin'); } use CGI; $query = new CGI; # # if existing parameters, get value for user_name if ($query->param()) { $user_name = $query->param('user_name'); } print $query->header; print $query->start_html('User Name Status'); # # use grep to check for user name $grep_string = "grep 'user_name=" . $user_name . "' userbin.inp |"; open(MASTER, $grep_string); read(MASTER, $result, 100); # # if not found, add, else give message of duplicate if (index($result, $user_name) > 0) { print $query->h1('Name already exists in system'); print "<p> That name already exists.<p>"; print "To try another go "; print $query->a({href=>"http://unix.yasd.com/unique/add_user.html"},"here"); } else { &add_choices($query); } # finish document print "<hr>"; print $query->end_html; exit(0); # # Subroutine add_choices # # Access query sent from form with options # Add user_name as first entry, then # add each of the options, formatted # # output line to userbin.inp file sub add_choices { my($query)=@_; $name=$query->param('user_name'); $query_line = $name . ":"; $company = $query->param('Company'); if ($company) { $query_line = $query_line . ":Company"; } $products = $query->param('Products'); if ($products) { $query_line = $query_line . ":Products"; } $prodx = $query->param('ProductX'); if ($prodx) { $query_line = $query_line . ":ProductX"; } $prody = $query->param('ProductY'); if ($prody) { $query_line = $query_line . ":ProductY"; } open(USER_FILE, ">> userbin.inp") || die "Could not process request"; $query_line = $query_line . "\n\n"; print USER_FILE $query_line; close(USER_FILE); print $query_line; print $query->h1('Your page information has been added'); print "<p> To try your page go "; print $query->a({href=>"http://unix.yasd.com/unique/index.cgi"},"here"); } The application creates a line in the USERBIN.INP file that looks similar to any one of the following: joeg::Company:ProductY tester::Products:ProductX sallybrown::Company:Products:ProductX
An additional enhancement that you could make in this application would allow the Web page reader to modify his choices. Currently, the application does not allow modifications after the user makes his choices.
When accessing his or her user-defined Web pages, the reader really is accessing the same CGI application as every other person who defined a user page. This application, called index.cgi, resides in a password-protected subdirectory. As stated previously, Chapter 8 "Understanding Basic User Authentication," provides the information necessary to set up this type of subdirectory.
Because the server is defined to look for certain files-such as INDEX.htmL, INDEX.htm, INDEX.SHTML, and eventually INDEX.CGI-when a browser accesses a subdirectory, this application runs as soon as the subdirectory is accessed. First, the server determines that the file exists in a protected subdirectory and requests that the user enter his or her user name and a password. These entries are verified with the .HTPASSWD file; then the application runs.
Because the user's identity was verified by means of authentication, certain CGI environment variables are set. Chapter 6 "Using Dynamic Pages," contains a list and description of several CGI environment variables. In this case, the one variable that is of interest is REMOTE_USER. This variable contains the name that the user entered as his user name; it should also be the name that the user entered when he defined his Web page.
The index.cgi application accesses the REMOTE_USER variable and then accesses the options that the reader specified for his other page. The application parses the options one at a time and then builds the page by accessing the subroutine that processes that particular option. Listing 7.3 shows the code for the main program of index.cgi.
Listing 7.3 Application to Access User-Specified Options and Call the Appropriate Subroutines (index.cgi)
#!/usr/local/bin/perl # # index.cgi # # Opens userbin.inp and pulls in user's choices. # BEGIN { push(@INC,'/opt2/web/guide/unix/book-bin'); } use CGI; $query = new CGI; # # printer document header $user_name = $ENV{'REMOTE_USER'}; print $query->header; print $query->start_html('The Unique Page'); print $query->h1("Some Software Company"); # # use grep to check for user name $grep_string = "grep '" . $user_name . "' userbin.inp |"; open(MASTER, $grep_string); read(MASTER, $result, 100); close(MASTER); # # if not found, return, else give pull in components if (index($result, $user_name) >= 0) { &get_options($user_name); } else { ¬_found($user_name); } print "<ADDRESS> <a href='mailto:shelleyp\@yasd.com'>Webmaster</a></ADDRESS>"; print $query->end_html; exit(0);
Notice that the Webmaster mailto is added at the bottom after all the components have been added and the page is complete.
At least two options determine how to add the components of each section of the Web page that is being built. One option is to use Perl to print the component content. The advantage of using this method is that you could use the same technique to generate all the sections of the document. The Perl library CGI.pm is particularly suited for this purpose, because it contains defined objects that can easily create whatever content is needed for the page.
Listing 7.4 shows the subroutine that creates the Company Announcements component.
Listing 7.4 Subroutine That Generates the General Company Announcements (index.cgi)
Sub print_company { print "<FONT SIZE=5 COLOR='#FF0000'>RECORD EARNINGS!</FONT>"; print "<p>"; print "Some Software Company has announced that they have exceeded all"; print "industry experts and have made a whopping $50,000,000.00 this last"; print "quarter. When the news was announced, stockholders, all two of"; print "them, collapsed in shock. Some Software Company CEO Joe Software"; print "has stated that this is the beginning only. He is quoted as saying"; print "'Software today, the World tomorrow.' This reporter is assuming"; print "he is talking about distribution only."; }
To pass the CGI object to the function, the first line of the subroutine is:
local($query)=@_;
The result of this code is the Web page shown in figure 7.3.
Figure 7.3 : This Web page displays the HTML generated from the majannounce CGI subroutine.
Another approach is to open a file containing the HTML statements and basically print the contents out as is. Listing 7.5 shows the subroutine that generates the Company Announcements section, using this technique.
Listing 7.5 Subroutine to Read Company Announcements and Print Directly (index.cgi)
# # subroutine print_company # # Open and print the contents of the company announcements file. sub print_company { open(COMPANY, "../book-html/maj_announce.html"); print "<HR><p>"; while (<COMPANY>) { print $_; } close(COMPANY); }
The advantage of using this technique is that someone who is not proficient in using Perl can create the content independent of the application. Also, this approach is simpler to implement, because the HTML document basically opens, prints, and then closes an HTML document.
The second technique is used for the example presented in this section. Listing 7.6 shows the code for all the subroutines.
Listing 7.6 Subroutines to Create the User-Defined HTML Document Contents (index.cgi)
# # subroutine get_options # # This subroutine will access the choices for the reader from # the userbin.inp file. For each option, the subroutine that contains # the handling of the option is called. sub get_options { my($name)=@_; # # find options line and load into variable open(CHOICES, "userbin.inp"); while (<CHOICES>) { $line=$_; if (index($line,$name) >= 0) { $data_string = $line; last; } } close(CHOICES); # # check for each option and call associated subroutine $line = $data_string; if (index($line, "Company") >= 0 ) { &print_company(); } if (index($line, "Products") >= 0) { &print_products(); } if (index($line, "ProductX") >= 0) { &print_productX(); } if (index($line, "ProductY") >= 0) { &print_productY(); } } # # subroutine not_found # # If there were no options for this reader, a # message is output and a link provided to # allow the reader to add options sub not_found { my($name)=@_; print "<HR><p>"; print "There are no options for " . $name . ".<p>"; print "set options <a href='http://unix.yasd.com/book-bin/binchoose.cgi'"; print "?user_name=" . $name; print ">here</a> to create your dynamic page."; } # # subroutine print_company # # Open and print the contents of the company announcements file. sub print_company { open(COMPANY, "../book-html/maj_announce.html"); print "<HR><p>"; while (<COMPANY>) { print $_; } close(COMPANY); } # # subroutine print_products # # Open and print the contents of the general products # announcements file. sub print_products { open(PRODUCT, "../book-html/prod_announce.html"); print "<HR><p>"; while (<PRODUCT>) { print $_; } close(PRODUCT); } # # subroutine print_productX # # Open and print the contents of the product X file. sub print_productX { open(PRODX, "../book-html/prodx_announce.html"); print "<HR><p>"; while (<PRODX>) { print $_; } close(PRODX); } # # subroutine print_productY # # Open and print the contents of the product Y file. sub print_productY { open(PRODY, "../book-html/prody_announce.html"); print "<HR><p>"; while (<PRODY>) { print $_; } close(PRODY); }
The last step in creating the user-defined HTML document is creating the components that are used. Listing 7.7 shows the HTML that creates the Company Announcements component. This component contains a headline and an announcement of record earnings for the quarter.
Listing 7.7 Company Announcements HTML Document (maj announce.html)
<!-- Major company announcement --> <FONT SIZE=5 COLOR="#FF0000">RECORD EARNINGS!</FONT> <p> Some Software Company has announced that they have exceeded all industry experts and have made a whopping $50,000,000.00 this last quarter. When the news was announced, stockholders, all two of them, collapsed in shock. Some Software Company CEO Joe Software has stated that this is the beginning only. He is quoted as saying "Software today, the World tomorrow." This reporter is assuming he is talking about distribution only.
The next component to implement is the Product Announcements page. This page contains an announcement of a new product that is in production and is expected to be delivered by the end of the year. Listing 7.8 shows the HTML for this component.
Listing 7.8 Major Product Announcement HTML Document (prod_announce.html)
<!-- Product Announcement --> <H2> New product, Product Z, to be released at year end </H2> Some Software Company has announced a new product, Product Z, that is expected to be ready for distribution. Unlike some other software programs that do everything "but wash your windows," Product Manager Paula Produce states "Product Z will add extensibility to any existing software products that will add in window washing capability." Industry experts are awed.
The next component contains a product announcement related specifically to Product X. The announcement states that Product X is being recalled, because the government has found that it tends to erase hard drives if it is brought within 25 feet of a computer. Listing 7.9 contains the code for this component.
Listing 7.9 Product X Announcement HTML Document (prodx_announce.html)
<!-- Product X announcement --> <H2> Product X recall </H2> Product X, also know in the industry as Solv-Ur-Problems, is being recalled by the Federal Government. Product X is an all purpose software program guaranteed to solve all your existing software problems. The government has found, though, that Product X will erase any and all hard drives that it comes within 25 feet of. <p> Paula Product, Product Manager, is quoted as saying "While we will comply with the government request to pull this product, we want to re-assure people that Product X does live up to it's claim of solving any software problem.."
The next component is for Product Y. There are no major announcements for Product Y, so the file is empty. The application can deal with empty files.
The result of all this effort is that a Web page reader with the user name sallybrown has chosen to read Company Announcements, Product Announcements, and Product X Announcements only. When she accesses the dynamic Web page site, her page looks like the one shown in figure 7.4.
Another Web page reader with the user name joeg chose the Company Announcements and Product Y Announcements options. His page is similar to the one shown in figure 7.5.
Figure 7.5 : This dynamic Web page was generated for user joeg, listing Company Announcements only.
No section is generated for Product Y, because the file that contains this information is empty. The file could have contained a line stating that there is no new information, or it could contain the most recent announcement, regardless of when it was made.
A Web page reader would follow these steps to set up a dynamic Web page:
Server-side includes (SSI) are directives that are processed at the server before the HTML document is sent to the client. The server has to parse the file one line at a time, looking for something that it should process.
The good news about SSI is that they are very easy to use. The bad news about SSI is that they can be a burden on the server and can also decrease the performance of the Web site. The server normally adds a response header to a document and then sends the document to the browser. With SSI, the server then has to scan each line. Any time that the server has to perform additional processing or handling of the document before transmission, that server's task is going to take valuable resources and degrade the performance of the Web site.
Some judicious use of this technology, however, can make SSI an effective Web tool.
If a site has 50 or 60 Web pages, and each Web page has the same images or text heading each page, you should consider implementing SSI. This technique allows the site to create one HTML document with the header that is then included in all the other documents. If that header is ever changed, the change would be reflected in all the documents.
One of the commands is the include command, which accesses a file and adds the contents of that file where the command is embedded in the document. With this type of command, the Web page developer for the hypothetical site could create one heading HTML file and use SSI to include this heading in each of the site's Web pages. The developer is spared from having to repeat the same statements again and again, and it is extremely easy to make changes in the heading and have those changes propagated across the entire site.
How difficult is it to use SSI? The format of an SSI command is:
<!-- #SSI_command arguments="values" -->
That's it, and that's why SSI is so popular. The following sections discuss setting up an environment for using SSI commands, the commands, and their arguments, and provide some examples of their use.
Server-side includes are not implemented for all Web servers, but they are implemented with NCSA, and there is a workaround with CERN servers.
Setting up the site requires the modification of the file named SRM.CONF, which is the server configuration file. Add the extension of the file that will contain the HTML to be parsed by adding a line similar to the following:
AddType text/x-server-parsed-html .shtml
With this line, any file with the extension .SHTML will be parsed by the Web server to look for SSI commands. The most common file extension used is .SHTML. This extension prevents the server from parsing any file with the .htmL extension, which could slow performance on HTML documents that do not include SSI commands.
A line needs to be added to the ACCESS.CONF file to enable SSI. To enable SSI with the exec command option, add the following line:
Options Includes
To enable SSI without the exec command, add the following:
Options IncludesNoExec
To enable a CERN Web server, a file called FAKESSI.PL is used. This file enables the server to emulate server-side includes. With this file installed, the server can process everything but the cmd option on an exec SSI.
To install this file, copy it to the /CGI-BIN subdirectory, and change the permissions on it to make it executable. In the HTTPD.CONF file (which is located in the Web server configuration file subdirectory, as it is defined for your system), add the following line:
Exec /*.shtml /users/www/cgi-bin/fakessi.pl
After the server is restarted, SSI commands will be enabled.
The SSI commands are:
include
config
flastmod
fsize
echo
exec
The format of an SSI command is:
<!--command arg="value" -->
It is extremely important to notice the spacing in this command. There can be no spaces between the first two dashes and the command, and there must be a space between the value and the last two dashes.
The include Command This command has two arguments: virtual and file. Using file allows the developer to specify subdirectories relative to the current directory, where the HTML document is contained. An example of this command is:
<!--#include file="/support/top_main.html" -->
With the virtual argument, the command changes to:
<!--#include virtual="/main_grp/top/support/top_main.html" -->
The virtual argument defines the path relative to the root directory on the server.
The included file cannot be a CGI application, but it can be an HTML document that contains a reference to a CGI application.
Figure 7.6 shows an example of an include SSI. The Web page displayed in this figure includes an HTML document that in turn contains a general header that is included in all the documents.
The main HTML document is called MAIN.SHTML. The .SHTML extension is the one that is defined for a SSI. Listing 7.10 shows the source code for this document.
Listing 7.10 HTML Document Containing a Server-Side Include with the include Command (main.shtml)
<HTML> <HEAD><TITLE>Some Software Company - Main Page </TITLE> </HEAD> <!--#include file="support/top_main.html" --> <p> Welcome to our web page! </BODY> </HTML>
The included file contains the beginning of the BODY section, which also defines the page colors, a header, and a graphic. Listing 7.11 displays the HTML for this document.
Listing 7.11 HTML Document Containing Header Section (top_main.html)
<BODY BGCOLOR="#FFEBCD" TEXT="#8B4513"> <H1>Welcome to Some Software Company!</H1> <p> <IMG SRC="../images/garden2.jpg"> <p>
The config Command The config SSI is used to modify the results of other SSI commands. The following command line modifies the error message that is returned when an SSI directive fails:
<!--#config errmsg="We're sorry but we are temporarily having difficulty opening this page." -->
Another argument to use with the config command is sizefmt. The following command line modifies the formatting of the size that is returned when the SSI fsize is used:
<!--#config sizefmt="bytes" -->
This command formats the return result of the fsize command in a display showing the full bytes of the file. An alternative argument is sizefmt='abbrev', which returns the size of the file, rounded to the nearest kilobyte.
The last argument that can be used with the config SSI is timefmt, which formats the return result with the flastmod SSI command. The flastmod command returns the last-modification date for the document. Following is an example of this command, which displays the date as the weekday and the date as abbreviated decimals separated by a slash:
<!--#config timefmt="%A %m/%d/%y" -->
The flastmod Command This SSI command displays the date when the HTML document was last modified. Like the include command, the flastmod command can be specified with the file argument or the virtual argument. Figure 7.7 shows an example of this command.
Figure 7.7 : This HTML document contains three SSI commands.
Listing 7.12 shows the HTML statements that generate this document.
Listing 7.12 HTML Document Containing Three SSI Commands (main2.html)
<HTML> <HEAD><TITLE>Some Software Company - Main Page </TITLE> </HEAD> <!--#include file="support/top_main.html" --> <p> Welcome to our web page! <p> This document was last modified on: <!--#config timefmt="%A %m/%d/%y" --> <!--#flastmod file="main2.shtml" --> </BODY> </HTML>
The fsize Command The fsize command returns the size of a file. Like the include command, fsize can access a file with the file argument or the virtual argument. To use this command, use the following syntax:
<!--#fsize file="main3.shtml" -->
The echo Command This command allows
the Web page developer to print certain variables that are specific
to it. Table 7.1 lists these variables.
Command Variable | Definition |
document_name | Main HTML document name |
date_gmt | Current date in GMT (Greenwich Mean Time) format |
date_local | Current date in local time zone |
Document_uri | Virtual path of document |
last_modified | Date document was last modified |
QUERY_STRING UNESCAPED_ | Unescaped version of any search query that the client sent, with all shell-specific characters escaped with a backslash (\) |
Figure 7.8 shows an example of using echo and other SSI commands, and Listing 7.13 shows the HTML that creates this document.
Listing 7.13 HTML Document Containing Several SSI Commands (main3.shtml)
<HTML> <HEAD><TITLE>Some Software Company - Main Page </TITLE> </HEAD> <!--#include file="support/top_main.html" --> <p> Welcome to our web page! <p> This document was last modified on: <!--#config timefmt="%A %m/%d/%y" --> <!--#flastmod file="main3.shtml" --> <p> The size of the graphic on this page is: <!--#fsize file="garden2.jpg" --> <p> This document is located at: <!--#echo var="document_uri" --> </BODY> </HTML>
The exec Command By now, you may be beginning to wonder how SSI commands fit in with a book about Perl. Any good developer uses multiple tools to do a job, and server-side includes are an effective tool. Additionally, you can include a Perl-based application by using the exec command. This SSI command runs an application and returns the result to the HTML document. This method is an effective approach to embedding in an HTML document information such as page access counts, a list of other people logged in, or any other command or application that returns a result.
Based on the power of this little command, this directive involves all sorts of security risks, and there is a good chance that it may be disabled for your system. "Setting up an Environment for Server-Side Includes" earlier in this chapter discussed how SSI commands are enabled and disabled. If you have access to exec in your system, however, this command provides an easy way to implement an access counter in your Web page.
Creating an access counter with SSI starts with creating a simple CGI program to open a file, get the count, increment the count, write back out to the file, and print to output. Listing 7.14 contains the Perl code for this application.
Listing 7.14 CGI Application to Increment Access Counter (accesses.cgi)
#!/usr/local/bin/perl # # accesses.cgi # # Application will access a value in a file, # increment it, write this new value out to the # file, and print the current value out to # standard output. # # print out content type print "Content-type: text/html\n\n"; # get current count open(COUNTER, "< access_counter") || die "BUSY"; $value = <COUNTER>; close(COUNTER); # increment count and write back out to file $value++; open(COUNTER, "> access_counter") || die "BUSY"; print COUNTER $value; close(COUNTER); # print $value; exit(0);
Next, access the CGI application by using the exec SSI command. Listing 7.15 shows the pertinent HTML statements.
Listing 7.15 Document That Contains Several SSI Commands (main4.html)
<HTML> <HEAD><TITLE>Some Software Company - Main Page </TITLE> </HEAD> <!--#include file="support/top_main.html" --> <p> Welcome to our web page! <p> This document was last modified on: <!--#config timefmt="%A %m/%d/%y" --> <!--#flastmod file="main3.shtml" --> <p> The size of the graphic on this page is: <!--#fsize file="garden2.jpg" --> <p> This document is located at: <!--#echo var="document_uri" --> <p> This site has been accessed <!--#exec cgi="accesses.cgi" --> times. </BODY> </HTML>
Each time the HTML document is accessed, the CGI application accesses.cgi
is called. This application increments a count that is then returned
to the HTML document.
File Locking |
Now is a good time to talk about file locking. If the ACCESS_COUNTER file in this example were not locked, two people accessing the site at the same time would get the same number. The first access would result in the existing number's being incremented and output to the counter file. The second access would increment the same number and output it. The result is that one count on the access counter would be lost. Over time, this situation could affect a busy site. One function to use is flock(), which locks the file and restricts two users from updating it at the same time. Unfortunately, this function uses the underlying operating-system command of the same name, and this command does not exist on many systems. In such a case, you could create a lock file when the file is opened. The application would test for the presence of this file when it wants to open the counter file. If the file exists, the application sleeps briefly. When the lock file does not exist, the application creates it, opens the target file, does what it needs to do with the file, and closes it. The last thing that the application does is remove the lock file. |
Shopping carts are applications that track information between several HTML documents. These applications usually use a combination of information stored at the client and information stored in a database or file at the server.
There are two approaches to shopping carts: storing information in form hidden fields or in client-side cookies. Form hidden fields are fields that store information and are not displayed to the Web page reader. Client-side cookies are small bits of information that are stored in a file (usually, COOKIE.TXT) at the client site. In both cases, the information that is stored usually is a file name or user name that is used to access the greater store of information at the server.
As stated in the preceding section, hidden fields are form fields that are used only for information storage and retrieval. Generating an HTML document with CGI allows input to these fields. Alternatively, you can set the fields by using a scripting language such as JavaScript.
One common use of hidden fields is to store a user name or file name that then can be used to reference and store information at the server. The information is passed when the form is submitted, and the CGI application that processes the form in turn uses the same information to set hidden fields on the next form that is displayed.
You also can send the information passed to the form by appending that information as name-value pairs in the URL of the document.
To demonstrate these concepts, the following listings create a small, simple electronic store called The Cyber Corner Market. This online store allows users to select fruits and vegetables, and stores the information in a file at the server. This file is created with the first order by using the user name and appending the day, month, and year to the name. The name and the user name are then passed to each of the documents and stored in hidden fields or processed directly.
Listing 7.16 contains code that creates the first HTML document of the market. The document contains a text field in a Web page reader can enter his name, as well as two radio buttons that allow the reader to specify whether he wants to order from the vegetable department or the fruit department.
Listing 7.16 CGI Application That Creates the Beginning Market HTML Document (market.cgi)
#!/usr/local/bin/perl # # market.cgi # # Creates HTML for beginning market order system. # Will check to see if user name and file name # are being passed into application. If they are # the values are stored in hidden fields. # # BEGIN { push(@INC,'/opt2/web/guide/unix/book-bin'); } use CGI; $query = new CGI; # # if existing parameters, get value for user_name if ($query->param()) { $user_name = $query->param('user_name'); } if ($user_name) { $file_name = $query->param('file_name'); } else { $user_name = "your_name"; $file_name = "your_name100.ord"; } # print header and heading information print $query->header; print $query->start_html('Cyber Corner Market'); print $query->h1('Cyber Corner Market'); print<<end_of_page; <HTML> <HEAD><TITLE> The Cyber Corner Market </TITLE> <SCRIPT LANGUAGE="JavaScript"> <!-- hide script from old browsers // this JavaScript function will create the file name function GenerateFileName(user_name) { var time = new Date() var time1 = time.getMonth() var time2 = time.getDay() var time3 = time.getYear() var filename = user_name + time1 + time2 + time3 + ".ord" document.MarketForm.file_name.value = filename } // this JavaScript function will access the appropriate CGI application function SubmitNow(category) { var suserfile = "user_name=" + document.MarketForm.user_name.value suserfile = suserfile + "&file_name=" + document.MarketForm.file_name.value if (category == "fruit") { window.location.href= "http://unix.yasd.com/book-bin/fruit.cgi?" + suserfile } else { window.location.href= "http://unix.yasd.com/book-bin/veggies.cgi?" + suserfile } } // end hiding from old browsers --> </SCRIPT> </HEAD> <BODY> <H1> Welcome to the Cyber Corner Market </H1> <p> <hr> <p> <H3> Please enter your name below and check which category you wish to shop from </H3> <p> <FORM name="MarketForm"> Your Name: <INPUT TYPE="text" size=30 name=user_name value=$user_name onChange="GenerateFileName(this.value)"> <p> <INPUT TYPE="hidden" name="file_name" value=$file_name> Fruit:<INPUT TYPE="radio" name="category" onClick="SubmitNow('fruit')"> <p> Veggies:<INPUT TYPE="radio" name="category" onClick="SubmitNow('veggies')"> <p> <hr> <ADDRESS> Webmaster </ADDRESS> end_of_page print $query->end_html; exit(0);
When the application is run for the first time, a default user name and file name are entered in the form fields, as shown in figure 7.9. When the Web page reader changes the user name, as shown in figure 7.10, a JavaScript function is called to create the new file name, which is then stored in a hidden field called file_name.
Figure 7.9 : This figure shows the first form of the Cyber Corner Market application.
Figure 7.10 : This figure shows the Cyber Corner Market after the user name has been changed.
If the reader chooses the veggies option, the next form (see fig. 7.11) displays a selection of vegetables.
Figure 7.11 : This form allows the reader to select vegetables from the Cyber Corner Market.
Notice from the Location toolbar that the user name and file name-in this case, customer and CUSTOMER6696.ORD-are passed to the new application. The application then stores this information in hidden fields. As the Web page reader enters the quantity that he wants, the total is calculated and placed in the Total field for each item. When the reader finishes and clicks the Send Order button, the form contents are processed by the next CGI application.
Listing 7.17 shows the CGI code for creating this form.
Listing 7.17 CGI That Accesses the Passed-In File Name and User Name, and Generates the Form (veggies.cgi)
#!/usr/local/bin/perl # # veggies.cgi # # Application will generate HTML for Cyber Corner Market, # vegetable section. # # Included in form will be hidden field containing # user_name and a hidden field containing file name. # # BEGIN { push(@INC,'/opt2/web/guide/unix/book-bin'); } use CGI; $query = new CGI; # # if existing parameters, get value for user_name if ($query->param()) { $user_name = $query->param('user_name'); $file_name = $query->param('file_name'); } # print header and heading information print $query->header; print $query->start_html('Cyber Corner Market-Veggies'); print $query->h1('Cyber Corner Market'); print<<end_of_page; <p> <hr> <p> <FORM name="veggies" ACTION="http://unix.yasd.com/book-bin/process_order.cgi"> <TABLE width=600 cellpadding=5 <STRONG> <tr><td>Quantity</td><td>Item</td><td>Total</td></tr></strong> <p> <INPUT TYPE="hidden" name="user_name" value=$user_name> <INPUT TYPE="hidden" name="file_name" value=$file_name> <tr><td> <INPUT TYPE="text" name="carrot_qty" size=5 onChange="document.veggies.carrot_total.value=parseFloat(this.value) * 1.10"> </td><td> Carrots - 1.10 per lb </td><td> <right><INPUT TYPE="text" name="carrot_total" size=10></right> </td><tr><td> <INPUT TYPE="text" name="tomatoes_qty" size=5 onChange="document.veggies.tomato_total.value=parseFloat(this.value) * .90"> </td><td> Tomatoes - .90 per lb </td><td> <INPUT TYPE="text" name="tomato_total" size=10> </td><tr><td> <INPUT TYPE="text" name="potato_qty" size=5 onChange="document.veggies.potato_total.value=parseFloat(this.value) * 1.50"> </td><td> Potatoes - 1.50 per 5 lbs </td><td> <INPUT TYPE="text" name="potato_total" size=10> </td><tr><td> <INPUT TYPE="text" name="corn_qty" size=5 onChange="document.veggies.corn_total.value=parseFloat(this.value) * .25"> </td><td> Corn - .25 per ear </td><td> <INPUT TYPE="text" name="corn_total" size=10> </td><tr><td> <INPUT TYPE="text" name="beans_qty" size=5 onChange="document.veggies.beans_total.value=parseFloat(this.value) * 2.0"> </td><td> Green Beans - 2.00 per lb </td><td> <INPUT TYPE="text" name="beans_total" size=10> </td></tr> <tr><td> </td><td> <INPUT TYPE="submit" value="Send Order"> </td></tr> </table> <p> <hr> end_of_page print $query->end_html; exit(0);
When the Web page reader submits the form, all the information-including the information in the hidden fields-is passed to the next application. By default, the application processes the form results, using the GET form-posting method.
The next component of the Cyber Corner Market application processes the order by appending it to the file and then provides options that allow the reader to continue or to finish the order, as shown in figure 7.12.
Figure 7.12 : This document is displayed after the reader submits a vegetable order.
Listing 7.18 shows the code to create the HTML and to process the order.
Listing 7.18 Application to Access the Order Elements That Are Passed in (process_order.cgi)
#!/usr/local/bin/perl # # process_order.cgi # # Application will access user name and # check for it in password file. # If name exists, reader will get a duplicate # name response and a link back to try again. # # BEGIN { push(@INC,'/opt2/web/guide/unix/book-bin'); } use CGI; $query = new CGI; # # if existing parameters, get value for user_name if ($query->param()) { $user_name = $query->param('user_name'); $file_name = $query->param('file_name'); } print $query->header; print $query->start_html('Confirm Order'); # # add order to file $person = "user_name=" . $user_name; $person = $person . "&file_name=" . $file_name; &process_order($query, $file_name); # finish document print $query->h1('Confirming order for ' . $user_name); print "<p>To continue ordering go "; print "<a href='http://unix.yasd.com/book-bin/market.cgi?"; print $person . "'>here.</a>"; print "<p>To finish go "; print "<a href='http://unix.yasd.com/book-bin/finish.cgi?"; print $person . "'>here.</a>"; print $query->end_html; exit(0); # # Subroutine process_order # # Access query sent from form with options # Add user_name as first entry, then # add each of the options, formatted # # output line to userbin.inp file sub process_order { local($query, $file_name)=@_; @fruits = (apple_total,grapes_total,pears_total, peaches_total,pineapple_total); @veggies= (carrot_total, tomato_total,potato_total, corn_total,beans_total); $name=$query->param('user_name'); $output_string = ""; @named_param=$query->param; if ($query->param()) { foreach $param_name (@fruits) { $val = $query->param($param_name); if ($val) { $output_string = $output_string . $param_name . "=" . $val . ":"; } } foreach $param_name (@veggies) { $val = $query->param($param_name); if ($val) { $output_string = $output_string . $param_name . "=" . $val . ":"; } } } open(USER_FILE, ">> " . $file_name) || die "Could not process request"; $output_string = $output_string . "\n"; print USER_FILE $output_string; close(USER_FILE); }
Notice in Listing 7.18 that the possible elements that can be passed to the application are placed in an array. Then each of these array elements is checked against the parameters passed to the application. If a parameter match is found, the value of the item and the item are output to file. Also notice that the file-name and user-name values are not maintained in hidden fields. These values are appended directly to URLs that are created for the document locations that the reader can access next.
If the reader were to continue ordering, he would return to the original document, shown in figure 7.13. This time, however, the name that the user specified is placed in the text field, and the created file name is placed in the hidden field.
Figure 7.13 : The application returns to the main Market form after processing the vegetable order.
Accessing the fruit option next, the reader sees a list of fruits. As in the vegetable form, the reader types the quantities, and the total is created from a JavaScript function, as shown in figure 7.14.
Listing 7.19 shows the CGI application for this form.
Listing 7.19 Application That Generates the Fruit-Ordering Document (fruit.cgi)
#!/usr/local/bin/perl # # fruit.cgi # # Application will generate HTML for Cyber Corner Market, # vegetable section. # # Included in form will be hidden field containing # user_name and a hidden field containing file name. # # BEGIN { push(@INC,'/opt2/web/guide/unix/book-bin'); } use CGI; $query = new CGI; # # if existing parameters, get value for user_name if ($query->param()) { $user_name = $query->param('user_name'); $file_name = $query->param('file_name'); } # print header and heading information print $query->header; print $query->start_html('Cyber Corner Market-Veggies'); print $query->h1('Cyber Corner Market'); print<<end_of_page; <p> <hr> <p> <FORM name="fruit" ACTION="http://unix.yasd.com/book-bin/process_order.cgi"> <TABLE width=600 cellpadding=5 <STRONG> <tr><td>Quantity</td><td>Item</td><td>Total</td></tr></strong> <p> <INPUT TYPE="hidden" name="user_name" value=$user_name> <INPUT TYPE="hidden" name="file_name" value=$file_name> <tr><td> <INPUT TYPE="text" name="apple_qty" size=5 onChange="document.fruit.apple_total.value=parseFloat(this.value) * .80"> </td><td> Apples - .80 per lb </td><td> <INPUT TYPE="text" name="apple_total" size=10> </td><tr><td> <INPUT TYPE="text" name="grapes_qty" size=5 onChange="document.fruit.grapes_total.value=parseFloat(this.value) * 1.50"> </td><td> Seedless Green Grapes - 1.50 per lb </td><td> <INPUT TYPE="text" name="grapes_total" size=10> </td><tr><td> <INPUT TYPE="text" name="pears_qty" size=5 onChange="document.fruit.pears_total.value=parseFloat(this.value) * 1.80"> </td><td> Pears - 1.80 per lb </td><td> <INPUT TYPE="text" name="pears_total" size=10> </td><tr><td> <INPUT TYPE="text" name="peaches_qty" size=5 onChange="document.fruit.peaches_total.value=parseFloat(this.value) * 1.80"> </td><td> Peaches - 1.80 per lb </td><td> <INPUT TYPE="text" name="peaches_total" size=10> </td><tr><td> <INPUT TYPE="text" name="pineapple_qty" size=5 onChange="document.fruit.pineapple_total.value=parseFloat(this.value) * 2.0"> </td><td> Pineapple - 2.00 each </td><td> <INPUT TYPE="text" name="pineapple_total" size=10> </td></tr> <tr><td> </td><td> <INPUT TYPE="submit" value="Send Order"> </td></tr> </table> <p> <hr> end_of_page print $query->end_html; exit(0);
As in the vegetable order form, clicking the Send Order button calls the process_order.cgi application, which appends the order to the file. At this point, the reader can choose to finish processing the file, which brings up a document listing his order (see fig. 7.15).
Figure 7.15 : The final document of the Cyber Market form lists the items ordered and their totals.
The application that generates this form accesses the file that contains the order information and displays the values. The application then uses the unlink Perl function to remove the link to the file. Because there is only one link to the file, the file is deleted.
Listing 7.20 shows the code for this component.
Listing 7.20 Application to Finish Processing the Order and to Destroy the Order File (finish.cgi)
#!/usr/local/bin/perl # # finish.cgi # # Finalized order for the Corner Cyber Market # # opens order file and loads each total into # array and prints each one out # # When done, the file is closed and deleted. # BEGIN { push(@INC,'/opt2/web/guide/unix/book-bin'); } use CGI; $query = new CGI; # # if existing parameters, get value for user_name if ($query->param()) { $user_name = $query->param('user_name'); $file_name = $query->param('file_name'); } print $query->header; print $query->start_html('Confirm Final Order'); print $query->h1("Finalizing order for " . $user_name); # open order file and print out totals print "<p><hr><p> Following are the items and total value for each <p>"; open(USER_FILE, "< " . $file_name) || die "Could not open order file."; print "<CENTER><TABLE width=200>"; foreach $line(<USER_FILE>) { @order_line = split(/:/,$line); print "<tr>"; foreach $elem (@order_line) { ($object,$value) = split(/=/,$elem); if ($value <= 0) { last; } print "<td>" . $object . "</td><td>"; printf "%7.2f", $value; print "</td></tr>"; } } print "</TABLE></CENTER>"; close(USER_FILE); unlink($file_name); print "<p><hr><p><h3>Thanks for your order</h3>"; print $query->end_html; exit(0);
The application described in this section is an extremely simplified version of more sophisticated ordering systems. This setup has several potential problems, including the following:
Cookies are an HTTP extension that began with Netscape. The concept is that a little bit of information is passed in the HTTP response header. The browser then processes the information and either stores it in a file (usually, COOKIE.TXT) on the client or accesses information in the same file to pass to the newly opened document or application.
The syntax for a cookie is:
Set-Cookie: name=value; expires= ; domain= ; path=\
Name is the name of the cookie, and Value is the value of the cookie. These values are the only required values. Expires is the expiration of the cookie, and by default, the cookie expires as soon as the current browser session terminates. The domain defaults to the server that generated the cookie, and the path defaults to the path of the Uniform Resource Identifier that sent the cookie.
Following is an example of setting a cookie:
Set-Cookie: user_name=test; expires=Tuesday, 12-Jun-96 10:00:00 GMT;
Cookies have limitations. There can be no more than 20 cookies from a domain or server, a cookie cannot be larger than 4K, and there can be only 300 cookies at the client.
When the cookie is set, each time the client requests a document in the path, it also sends the cookie, as follows:
Cookie: user_name=test
Using the CGI.pm Perl library, you set the cookie by using the cookie() method, as follows:
$cookie1=$query->cookie(-name=>"user_name",-value=>$user_name, expires=>"+1h");
The string is then sent in the header request, as follows:
print $query->header(-cookie=>$cookie1);
Multiple cookies can be sent using an array reference, as follows:
print $query->header(-cookie=>[$cookie1,$cookie2]);
To access the cookie, use the same cookie method, but this time pass in the name parameter, as follows:
$user_name = $query->cookie(-name=>'user_name');
NOTE |
Although cookies are handy and a highly effective way of passing information between document pages at a site, their biggest limitation is that Netscape Navigator and Microsoft's Internet Explorer are the only browsers that are capable of processing them at this time. |
For information on related topics, you may want to read the following chapters: