3  Interactive CGI Scripts

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.

Figure 20:  The two interfaces of the interactive CGI server

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 becomes

(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:

;; main : Bindings -> void
;; to produce one of two Web interfaces
;; for an interactive finger server
(define (main bindings) ...)

Since 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 script invokes main with empty, which then produces the query interface for the finger server.


Exercises

Exercise 3.0.7.  

Exercise 3.0.8.  


(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

Figure 22:  The history-sensitive interface of the interactive CGI 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.


Exercises

Exercise 3.0.9.  

Exercise 3.0.10.  



12 The action attribute in this code is not a full URL.