The most interesting CGI scripts are interactive. That is, they acquire some information from the user and then prompt the user for more. They are just like ordinary interactive programs, except that they produce (X)HTML pages as output and read input in a non-standard manner.
Let's take a look at the finger server again. Instead of providing an initial Web page, we could provide a script that, when invoked without user-supplied data, generates a Web page that allows a user to finger another user or finger all users on a Web server. More precisely, when a user enters the URL of the interactive finger server, the script produces the Web page on the left of figure 20. After entering a name and clicking the FINGER button, the script displays the page on the right of the same figure. Finally, if the user wishes to make another query, a click on BACK puts the CGI script back into the initial state.
The finger servers of the preceding subsections differ from the interactive finger server illustrated in figure 20 in that the latter produces different Web pages depending on the bindings that the browser supplies to the server and thus the script. If the bindings is empty, the script produces the page on the left; otherwise, it produces the page on the right (for a given name).
Considering the complexity of the script, it is best to design a function
that consumes the bindings and produces the appropriate Web page in
response. If the function is called
main, the body of the script
(output-http-headers) (main (get-bindings))
The first expression produces the header; the second one the XHTML page itself.
Here are the basic specifications for
main : Bindings -> void;; to produce one of two Web interfaces ;; for an interactive finger server (define (main bindings) ...)
Bindings is a list,
main should at least
distinguish two cases, depending on whether
bindings is empty or
not. In both cases, the function must write a Web page, which means that
there is common code:
(define (main bindings) (cond [(empty? bindings) (write-xml/content (xexprxml `(html (title "Finger Gateway") ;; an X-expression that produces the Web page on the left ;; of figure 20 ...)))] [else (write-xml/content (xexprxml `(html (title "Finger Gateway") ;; an X-expression that produces the Web page on the right ;; of figure 20 for the given value of who ...)))]
We can avoid the code duplication by moving the inside of the X-expression. The definition in figure 21 does just that.
The first branch of the produces a large constant: an
X-expression that turns into a query form for the finger server. See
figure 21 for the precise specification.12 The second branch
of the consists of two parts: the result of running the Unix
finger program as a process on the given input, and a button for
going back to the original query interface. Running the finger
program and turning its output into XHTML is a variation on our running
theme of this part. Creating a button to take the user back to the
original query interface is also straightforward. Because the action
attribute points back to the same CGI script, a user who clicks on this
button re-invokes the script, but this time without any inputs. Hence the
empty, which then produces the
query interface for the finger server.
(define FINGER "/usr/bin/finger") (define THIS " ... /finger3.ss") ;;
main : Bindings -> void(define (main bindings) (write-xml/content (xexprxml `(html (title "Finger Gateway") ,(cond [(null? bindings) `(body ([bgcolor "beige"]) (h3 "Welcome to the Finger Gateway") (form ([action ,THIS] [method "post"]) (input ([type "text"][name "who"])) (input ([type "submit"][value "FINGER"]))))] [else (let ([the-user (extract-binding/single 'who bindings)]) `(body ([bgcolor "white"]) (p ,(stringhtml (read-line (car (process "date"))))) ,@(map (lambda (x) `(p ,(stringhtml x))) (run-finger the-user)) (hr) (form ([action ,THIS] [method "post"]) (input ([type "submit"] [value "Back to FINGER"])))))]))))) ;;
run-finger : string -> (listof string);; to ``finger'' a user on a Unix computer (define (run-finger who) (read-all (car (process (string-append FINGER " " who))))) ;;
read-all : iport -> (listof string)(define (read-all ip) (let ([next (read-line ip)]) (cond [(eof-object? next) '()] [else (cons next (read-all ip))]))) (output-http-headers) (main (get-bindings))
Figure 21: An interactive CGI finger server
Suppose the user of our finger server wishes the server to keep track of who was fingered. Specifically, when the server displays the results for a request, it also lists the who attributes of all the previous requests in this session with the server. Figure 22 shows a simple format for displaying the additional information.
At first glance, implementing this new functionality looks tricky. After all, the Web server invokes the finger script from anew every time it receives a request, with no obvious mechanism for distinguishing one client from another. Put positively: Given that the script initially only receives the input from the interactive Web form, the script must somehow hide information in the Web forms that it produces for future identification.
The HTML standard provides a mechanism for just this kind of information hidding.
Hence, to distinguish one session from others, we must somehow tag a session as soon as a user invokes the script and keep track of the tag for all future requests.
12 The action attribute in this code is not a full URL.