One problem from which newLISP suffers is the lack of a really useful library for web-based applications. The official CGI module has serious enough problems to justify an entirely new library. After some thought, I decided moreover that the request and response modules that I designed were neither practical nor sufficient. To that end, I have designed a new, monolithic library to provide the essential functionality required for web programming.
The official CGI module has some real issues. First, it combines POST and GET variables into a single structure. This has two serious consequences: 1) the application has no way to determine the method by which a parameter is passed (that information is completely lost), and 2) name clashes between GET and POST result in the loss of one or the other parameter (in the case of the CGI module, GET information would be overwritten.)
Another issue in the CGI module is with the
put-page function, which breaks if “%>” is used inside of a code island, even legitimately, such as in a string.
Web fixes both of these problems and provides a number of other features, including:
- ASP/PHP-style templates
- Getting/setting cookies, GET, and POST parameters
- Entity encoding and decoding
- HTTP header control
- Custom session storage
- URL building and parsing
- URL encoding and decoding
- Query string building and parsing
Additionally, Web does not suffer from the GET/POST issues that the CGI module does, nor does it mishandle tags inside of code.
Headers are set using
Web:header, which accepts two parameters – the header name and the header value. By default, one header is already set: Content-type: text/html. Headers are output using
Web:send-headers, which is called before any other output. The convenience function
Web:redir redirects the browser to the passed URL.
GET and POST
GET and POST variables are accessed using
Web:post, which may be called in two ways. When called with a single parameter, these functions act like
lookup and return the GET/POST parameter with the named key. When called with no parameters, they return an association list of the corresponding query.
Cookies must be set before headers are sent. They are set using the
Web:cookie function, which accepts up to six parameters.
;; Set a basic cookie (cookie "foo" "bar") ;; Access a cookie value (cookie "foo") ;; Delete a cookie by setting expires to now (cookie "foo" nil (date-value)) ;; Set a cookie that expires in an hour (cookie "foo" "bar" (+ (date-value) (* 60 60)))
See the documentation for a full list of accepted parameters.
By default, sessions use file-based storage (located at /tmp). They are controlled with a few simple functions. The default
exit function is wrapped to ensure that
Web:close-session is called at the end of a script (at least, a script that calls
exit at its end.)
(Web:open-session) (Web:session "foo" "bar") ; store "foo" as "bar" (Web:send-headers) ; start output (println "<p><strong>foo is:</strong> " (Web:session "foo") "</p>") (exit 0)
It is a simple matter to design and use custom storage handlers. The function
Web:define-session-handlers allows customization of which functions are called to begin/load, close/write, delete a session, and clear old sessions. See the documentation for a list of helpful functions and variables for custom storage handlers.
Templates work almost identically to the official CGI module, with a few differences. First,
Web:eval-template is called with a string, rather than a file, to permit other storage methods and programmatic building of templates. Second, <%= .. %> may be substituted for the default opening tag as a shortcut for <% (print … ) %>. Last, the opening and closing tags may be customized by setting values of
Web:CLOSE_TAG. The shortcut tag will always be
Web:OPEN_TAG appended with and equal sign.
Encoding and decoding
Web:escape takes a string and encodes the basic HTML character entities (apostrophe, quote, ampersand, and left and right angle brackets.)
Web:unescape provides the reverse. The function
Web:encode-entities encodes all HTML entities, including a number of entities that are not fully supported by all browser. The full list of entities is derived from Wikipedia. Its reverse,
Web:decode-entities, translates entities back into their character equivalents.
For URLs, it is simpler to use
Web:build-url, which takes a URL and any number of association lists which it uses to build a complete URL. Each alist may overwrite parameters from the previous, including any parameters on the passed URL.
(let ((url "http://www.artfulcode.net/?s=newlisp")) (println (Web:build-url url '(("s" "newlisp web module") ("foo" "bar"))))) ;; => http://www.artfulcode.net/?s=newlisp+web+module&foo=bar
It also has a counterpart,
Web:parse-url, which breaks a URL up into an association list of its component parts.