How to write CGI scripts

Type CGI in the search box below and click Search to get information on CGI-related functions.

A CGI script is merely a program with funny inputs and outputs. Input comes either from an environment variable or through the standard input port, in a special format. Output consists of a MIME header followed by the content. Everything inbetween is pure program.

MzScheme comes with a CGI library that is designed to make it easy to write such scripts. In this mini-tutorial, we'll walk you through the construction of such a script. If you have questions or comments, send email to shriram@cs.rice.edu.

----------------------------------------------------------------------

Let's write a simple "finger server" in MzScheme. The front-end will be a Web form that accepts a username. The form should supply a username in the field `name'. The CGI script fingers that user.

First, make sure you have MzScheme installed on your Web server.

A CGI script must be an executable. Each OS has different ways of launching an application. Under Unix, it's probably easiest to make them simple shell scripts. Therefore, place the following magic incantation at the top of your script:

  #!/bin/sh
  string=? ; exec /usr/local/bin/mzscheme -r $0 "$@"

(Make sure the path to MzScheme is specified correctly.)

Now we're in Scheme-land. First, let's load the Scheme CGI library and define where `finger' resides.

  (require-library "cgi.ss" "net")
  (define finger-program "/usr/bin/finger")

Next we must get the names bound by the form, and extract the username field.

  (let ((bindings (get-bindings)))
    (let ((name (extract-binding/single 'name bindings)))

We use extract-binding/single to make sure only one name field was bound. (You can bind the same field multiple times using check-boxes. This is just one kind of erorr-checking; a robust CGI script will do more.)

Next we invoke the finger program using `process*'. If no username was specified, we just run finger on the host.

  (let ((results (if (string=? name "")
         (process* finger-program)
         (process* finger-program name))))

The `process*' function returns a list of several values. The first of these is the output port. Let's pull this out and name it.

  (let ((proc->self (car results)))

Now we extract the output of running finger into a list of strings.

  (let ((strings (let loop ()
		   (let ((l (read-line proc->self)))
			     (if (eof-object? l)
			       null
			       (cons l (loop))))))) 

All that's left is to print this out to the user. We use the `generate-html-output' procedure to do that, which takes care of generating the appropriate MIME header (as required of CGI scripts). Note that the <PRE> tag of HTML doesn't prevent its contents from being processed. To avoid this (ie, to generate truly verbatim output), we use `string->html', which knows about HTML quoting conventions.

  (generate-html-output "Finger Gateway Output"
    (append
      '("<PRE>")
       (map string->html strings)
      '("</PRE>"))))))))

That's all! This program will work irrespective of whether the form uses a GET or POST method to send its data over, which gives designers additional flexibility (GET provides a weak form of persistence, while POST is more robust and better suited to large volumes of data).

Here's the entire program, once again:

  #!/bin/sh
  string=? ; exec /usr/local/bin/mzscheme -r $0 "$@"
 
  (require-library "cgi.ss" "net")
  (define finger-program "/usr/bin/finger")
 
  (let ((bindings (get-bindings)))
    (let ((name (extract-binding/single 'name bindings)))
      (let ((results (if (string=? name "")
                       (process* finger-program)
                       (process* finger-program name))))
        (let ((proc->self (car results)))
          (let ((strings (let loop ()
                           (let ((l (read-line proc->self)))
                             (if (eof-object? l)
                               null
                               (cons l (loop)))))))
            (generate-html-output "Finger Gateway Output"
              (append
                '("<PRE>")
                 (map string->html strings)
                '("</PRE>"))))))))