A better newLISP web library

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
  • Sessions
  • 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.

HTTP Headers

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:get and 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

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.

Sessions

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 "

foo is: " (Web:session "foo") "

"
) (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

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:OPEN_TAG and Web:CLOSE_TAG. The shortcut tag will always be Web:OPEN_TAG appended with and equal sign.

Encoding and decoding

There are several functions to make encoding, decoding, and escaping strings easier for dealing with URLs, javascript, and HTML entities.

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:escape-js does no encoding of entities, but instead ensures that a string may be safely output in javascript string without causing syntax errors.

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.

The functions Web:url-encode and Web:url-decode deal with hex-encoding/decoding strings for use in URLs.

Query strings are easily created using Web:build-query, modeled after the PHP function http_build_query. Its counterpart, Web:parse-query, takes a query string and turns it into an association list.

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 "https://artfulcode.net/?s=newlisp"))
  (println (Web:build-url url '(("s" "newlisp web module") ("foo" "bar")))))
;; => https://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.

Download

You can find it in the repository or download it directly here.

Leave a comment | Trackback
May 29th, 2009 | Posted in Programming, Software
Tags: ,
  1. Duke
    May 29th, 2009 at 14:28 | #1

    Thanks jeff! on my system this newLisp module will forever be know as oberWeb.lsp.

  2. Jul 27th, 2009 at 12:50 | #2

    Just thought I’d better call by and say “Thanks” again for this, Jeff. You can see it in action at … no wait, you can’t see it in action, because it’s working seamlessly in the background! :)

  3. Aug 6th, 2009 at 12:31 | #3

    jeff – I’ve been struggling with web.lsp a bit when it came to coding an HTML form, and I just thought I’d check with you… Is it possible to write a form where the textarea starts with a parenthesis character? So far all my attempts at getting such a string from the POST parameters haven’t worked…

    Basically I’m going:

    and I’m not seeing the textarea’s ‘name’ appearing. Am I doing something stupid? Perhaps I should be encoding the contents of the text area? :)

  4. Jeff
    Aug 6th, 2009 at 13:45 | #4

    What does it do with GET parameters? Try using the Web:parse-query routine directly on the query string and see what output it gives you.

  5. Aug 6th, 2009 at 17:13 | #5

    I can’t get the parse-query to work at all – keeps returning an empty string. But it’s not easy for me to extract that function out of context…

    My test form is here. It just displays the POST and GET parameters when you type in some text in the text area and click the buttons. Some text works, but not any text – generally it doesn’t like punctuation in the textarea. I can’t identify the pattern yet. Suggestions welcomed! :)

  6. Jeff
    Aug 7th, 2009 at 11:52 | #6

    I imagine it is in the regular expression that is parsing the query. I am on vacation until next Thursday. I will try and find some time to figure it out when I get back. If you find anything in the meantime, post it and I will update the code.

  7. Aug 7th, 2009 at 16:43 | #7

    OK, thanks Jeff. I think it’s that regex, but I really can’t make much of it… :( I want the form’s textarea to be able to submit any newLISP expression – not sure how the regex handles them.

    I could use cgi.lsp for this part, I suppose…

  8. Jeff
    Aug 13th, 2009 at 11:50 | #8

    I’ve updated the module to be more tolerant on query parsing as well has (hopefully) fixing the bug. Take a look and see if that solves the problem.

  9. Aug 13th, 2009 at 16:25 | #9

    Hi Jeff. Thanks for doing this! So far this new version looks like it’s doing exactly the right thing. No problems to report…!