Collection: cgi.ss (net)
Collection: xml.ss (xml)
Help Desk: CGI, XML
Over the past few years, a new form of program has appeared: the CGI script. Roughly speaking, a CGI script is just a program that is invoked by a Web server, receives its inputs from the server, and communicates its result to the server. More technically, a web server interprets HTTP requests. Some such requests demand that the server start a program. One portion of such a request specifies the name of the script and its locations; another specifies the inputs for the script. After the script is running, it computes an output using the server-supplied inputs. The server returns this output as the response to the original HTTP request. The interface between the server and the scripts is called the Common Gateway Interface.
This part illustrates how to write CGI scripts that produce Web pages. The first section looks at simple scripts, that is, programs that compute a single response to a request. The second section concerns interactive CGI scripts that implement interactions with many steps.
Like any other program, a CGI script can generate an output without ever
looking any inputs. The only requirement is that a CGI script must print
an HTTP header followed by some HTML content9 to the standard
output. The first task is accomplished with the function
output-http-headers; it consumes no output and prints an HTTP
header suitable for transmitting HTML text. For the second task, the CGI
programmer can use whatever is most convenient. We use the XML library
here (see section 2).
Let's look at a couple of examples. The first one produces a ``hello world'' page in response to a request:
(output-http-headers) (printf "<html>~n") (printf "<title>My First CGI Web Page</title>~n") (printf "<body><h1>Hello World</h1></body>~n") (printf "</html>")
It consists of two parts: one for printing the HTTP headers and one for
printing the HTML page. For the second step, it employs a sequence of four
printfs. This style of writing CGI scripts is reminiscent of Perl
scripts. Of course, with Scheme we can do better.
The second CGI script produces the same output as the first, but uses the XML library and X-expressions instead of plain strings:
(require-library "xml.ss" "xml") (output-http-headers) (write-xml/content (xexprxml '(html (title "My First CGI Web Page") (body (h1 "Hello World")))))
It constructs the HTML page as an X-expression that is then translated into XML and printed as such. The advantage is that the parenthetical structure of the page is obvious to any reader of the CGI script and doesn't have to be deduced from string-based output statements.
The scripts print the following output:
Content-type: text/html <html><title>My First CGI Web Page</title> <body><h1>Hello World</h1></body> </html>
The first line is the HTTP header; the second is the ``hello world'' page.10
; #! /bin/sh
; string=?; exec /usr/local/lib/plt/100/bin/mzscheme -q -r "$0" "$@" (require-library "cgi.ss" "net") (require-library "xml.ss" "xml") (output-http-headers) (write-xml/content (xexprxml '(html (title "My First CGI Web Page") (body (h1 "Hello World")))))
Figure 15: A complete CGI script
Once we have a program that produces the proper output, we must turn it into a CGI script proper. To do so requires two steps. First, we must turn it into a stand-alone script. Following part IV, we just add two lines to the head of the program: see figure 15. The second line specifies the complete path to the executable Scheme implementation on the Web server. Second, we must tell the operating system that the file is an executable program. See your operating system manual for the precise actions on how to do that.
At first glance, a CGI script that doesn't query its inputs is useless because it produces the same output for each request. This doesn't have to be the case, however. To produce some output, a CGI script may query the status of its machine. For example, it may run the Unix finger program and translate its results into HTML; it may query the mail server and output the mailbox for a specific user; or it may inspect the status of some device -- say a coke machine -- and report what it found.
To make this discussion concrete, let's design a program that ``fingers'' the users on the machine. The Unix finger program reports some statistics on each user who is currently logged on. We can run Unix programs from within Scheme with the process primitive (see part IV):
> (process "/usr/bin/finger") (#<stream-input-port> ...)
The primitive creates an independent process and produces a list of results. The first one is an input port (see part II); the remaining values don't play any role for the finger example. Here the port contains the output of running finger, and the Scheme program can read it and format it as desired.
(define FINGER "/usr/bin/finger") ;;
read-all : iport -> (listof string)(define (read-all ip) (let ([next (read-line ip)]) (cond [(eof-object? next) '()] [else (cons next (read-all ip))]))) ;; --- the script --- (output-http-headers) (write-xml/content (xexprxml `(html (title "Finger Gateway") (body ([bgcolor "white"]) (p ,(stringhtml (read-line (car (process "date"))))) ,@(map (lambda (x) `(p ,(stringhtml x))) (read-all (car (process FINGER))))))))
Figure 16: A simple CGI finger server
Figure 16 presents the complete finger program. The
read-all reads all lines from a port. The last two
expressions are the familiar components of a CGI program. Instead of
producing a constant X-expression, however, this program creates an
X-expression by running date and finger as processes and by
turning their results into HTML text. The function
is also a part of the CGI library; it turns ordinary strings into strings
that use the HTML quoting conventions. Each HTTP request for this script
will produce a different output, starting with a new date and time.
9 It is also possible to write CGI scripts that produce images or sounds.
10 The true output consists of just two lines.