a web-framework using Armed Bear Common Lisp as a Java Servlet
ABCL-web's web-engine is a very simple (essentially, it's just two macros!) 'framework' for developing web applications. It's very good for prototyping, but might be not useful for production use. It can be called continuation-based, however it doesn't do CPS-transform itself -- developer should write continuation-functions explicitly himself.
HTML markup is done via LML2. make-page macro can be used to redirect HTML output to the string and define standard prologue and head with specified title:
(defun start-page () (make-page "start page" ;;page title (:body (:h1 "start page") (:p "hello wold"))))
Start page -- a start-page function -- is an entrance-point for users. All other pages and transitions are controlled via action macros.
The base of web-engine consists of two 'action' macros -- action-link and action-form. Action-link defines a link, which executes specified action (arbitrary forms) when executed:
(action-link "link text" (form1) (form2) ..)
This macro creates a closure and associates an indentifier with it. This indentifier is embedded in a link, when user will activate link, closure will be executed (it's possible to execute it more than once). Forms in action-link can be arbitrary code, it can call other functions, refer to lexical variables (it's a closure) etc. Typically each web-page is just a function defined with defun, and action-link just contains a call to that function, either with parameters or without. For example:
(defun start-page () (make-page "start page" (:body (:h1 "start page") (:p (action-link "next page" (next-page)))))) (defun next-page () (make-page "next page" (:body (:h1 "next page") (:p (action-link "start-page" (start-page))))))
This is a complete "web application" consisting of two pages referencing each other.
Here's an example of action-link with action code referencing local variables (nore: we need to rebind loop variable, otherwise all closures will point to same variable):
(html (:table (loop for i from 1 to 10 do (html (:tr (:td (:princ i)) (:td (let ((i i)) (action-link "click" (click-page i)))))))))
Action-form works like action-link, but it makes a form with inputs, for example:
(action-form (((:input :name "a")) ((:input :name "b")) (form-submit "add")) (with-form-fields (a b) (show-result-page a b)))
Macro action-form will create a form with a hidden cont-id input and user-defined inputs (including a convenience alias form-submit). When user will activate this form, filling inputs possible, code will be called. Macro with-form-fields can be used to extract form fields and to bind them to local variables, but it just extracts them from a variable form-data containing plist of fields, so they can be extracted with getf: (getf form-data :a).
Lexical variables and function parameters can be safely used in action code, but special variables should be used with care, they can lead to unexpected results.
As mentioned above, entry point is start-page function. Web-engine does not force any further application structure, there's no need to register pages, actions or whatever, and it's technically possible to implement arbitrary complex web applicaton in a single start-page function. However, for better maintanability it's better to split functionality into multiple functions. For example, it might be a good structure to have a function for each web page, and to move through this pages via function calls in action-link and action-form.
While there's much less need of session variables with 'continuation' style programming, where values can be passed as function parameters, in some cases it might be more convenient to use special variables that will be preserved across action-form calls. As this is particulary useful for authentication/authorization purposes -- when user have authenicated, he's identity should be accessible at each point of application -- web-engine automatically preserves value of sepcial variable *user* across calls. So, variable *user* should be bound with let or set with setq after check of credentials -- and it would be available in all subsequent 'pages'.