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.
Thanks jeff! on my system this newLisp module will forever be know as oberWeb.lsp.
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! :)
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? :)
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.
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! :)
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.
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…
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.
Hi Jeff. Thanks for doing this! So far this new version looks like it’s doing exactly the right thing. No problems to report…!