A lazy CGI namespace in Scheme

 

A poster presentation at the 1998 International Conference on Functional Programming (ICFP'98)
Mt. Washington Conference Center, Baltimore, MD, 27-29 September 1998

 

Introduction

The subject of this presentation is a particular CGI scripting framework that allows:

This framework has been used extensively for several months in a production environment.

The framework features:


 

Importing from CGI#

A form

      (CGI:define name default-value)
defines an obscure datum and binds it to a import promise. The form also defines a macro -- cgi#name -- to access this obscure datum and perform typecasts on request.

The import promise parses the QUERY_STRING or a POST message and caches the result. The promise uses it when looking up all values of a given form parameter.

The following names are pre-defined (pre-imported) in the CGI namespace: cgi#query-string, cgi#query-parms, cgi#self-url
 

Type Casts

(cgi#keywords)
gives the first word of a form slot named keywords, or #f
 
(cgi#keywords :as-list)
the list of all the words or all chosen OPTIONs, or ()
 
(cgi#keywords :as-full-string)
all the keywords separated with a single space, or ""
 
(cgi#keywords :as-name-value-io)
a promise which will write out
    NAME=keywords VALUE=value
with appropriate quotations and escapes
cf. Haskell's IO
 
(cgi#keywords :set! new-value)
makes sense if the new-value is to be used when generating an updated cgi form

 

Example

Definition of CGI parameters

The following piece of code declares two CGI variables that are being used by the code below. These are import clauses from a CGI namespace into the Scheme environment. As a regular define, a CGI:define may appear both on the top lexical level and within a procedure/scope.

     (CGI:define block_id #f)
     (CGI:define name #f)

The root thunk of the script

Note that this piece of code has an internal CGI:define

     (with-catching-of-signals
       (lambda ()
         (CGI:define do-search 'absent)
         (mode-show-form
           (call-with-current-continuation
             (lambda (show-cont)
               (cond
                 ((not (eq? (cgi#do-search) 'absent))
                   (mode-search show-cont))
                 (else "<P>")))))))

Form Composition

     (define (mode-show-form arg-res)
       (cout "Content-type: text/html" 
             "\n\n"
             ...
         "<FORM METHOD=POST ACTION=" (cgi#self-url) ">\n"
         "<H3 ALIGN=CENTER>Specify search parameters</H3>"
         "<TABLE CELLPADDING=2 CELLSPACING=5 BORDER=0>\n"
         "<TR><TH>Station Block ID<TD>"
         "<INPUT TYPE=TEXT " (cgi#block_id :as-name-value-io)
         " SIZE=6 MAXLENGTH=6>\n"
         "<TR><TH>Station's full name<TD>"
         "<INPUT TYPE=TEXT " (cgi#name :as-name-value-io)
         " SIZE=19 MAXLENGTH=19>\n</TABLE>"
         "<INPUT TYPE=SUBMIT NAME=do-search VALUE=\"Find\"><P>"
         "</FORM><P><HR>\n"

       (if (procedure? arg-res) arg-res "")
       "</BODY></HTML>"))

Form Analysis

This function executes a search request submitted by a user via the form above.

     (define (mode-search esc-to-show)
        ...
       (when (cgi#name)
          (++! search-criteria-count)
          (sql-buffer 'accum-sql!
            " AND MS.name LIKE '"
            (string-upcase (cgi#name :as-full-string)) "'"))
        ...
       (if (zero? search-criteria-count)
         (esc-to-show
           "<P><B>Nothing to search for</B>"))
        ...
     )

 

Implementation

     (define-macro (CGI:define name . init-value)
       (let ((ext-name
               (string->symbol (string-append "_:CGI:" (symbol->string name))))
             (locname (gensym)))
      `(begin
         (define ,ext-name
            (delay (_:CGI:lookup ',name ,@init-value)))
         (define-macro
           ,(cons
               (string->symbol (string-append "cgi#" (symbol->string name)))
               'optional-arg)
          `(let ((,',locname (force ,',ext-name)))
             ,(cond
               ((null? optional-arg)
                 `(and ,',locname (car ,',locname)))
               ((equal? optional-arg '(:as-full-string))
                `(if ,',locname (apply string-append
                     (list-intersperse ,',locname " "))
                  ""))
               (else (##signal '##signal.syntax-error "cgi#" optional-arg)) ))))))

 


REFERENCES

A one-page abstract of this presentation [.ps.gz, 5K]

Other approaches
http://www.eval-apply.com/Scheme/cgi.htm

Parsing of a QUERY_STRING or a POST action message and converting them into an associative list
http://pobox.com/~oleg/ftp/Scheme/web.html#parsing.query-string

This example's complete source code
http://pobox.com/~oleg/ftp/Scheme/web.html#search-mslib


Last updated October 9, 1998

This site's top page is http://pobox.com/~oleg/ftp/

oleg-at-pobox.com or oleg-at-okmij.org
Your comments, problem reports, questions are very welcome!