A Small Application Framework
Although AllegroServe provides fairly straightforward access to all the basic facilities you need to write server-side Web code (access to query parameters from both the URL’s query string and the post data; the ability to set cookies and retrieve their values; and, of course, the ability to generate the response sent back to the browser), there’s a fair bit of annoyingly repetitive code.
For instance, every HTML-generating function you write is going to take the arguments request
and entity
and then will contain calls to with-http-response
, with-http-response
, and—if you’re going to use FOO to generate HTML—with-html-output
. Then, in functions that need to get at query parameters, there will be a bunch of calls to request-query-value
and then more code to convert the string returned to whatever type you actually want. Finally, you need to remember to publish
the function.
To reduce the amount of boilerplate you have to write, you can write a small framework on top of AllegroServe to make it easier to define functions that handle requests for a particular URL.
The basic approach will be to define a macro, define-url-function
, that you’ll use to define functions that will automatically be published via publish
. This macro will expand into a **DEFUN**
that contains the appropriate boilerplate as well as code to publish the function under a URL of the same name. It’ll also take care of generating code to extract values from query parameters and cookies and to bind them to variables declared in the function’s parameter list. Thus, the basic form of a define-url-function
definition is this:
(define-url-function name (request query-parameter*)
body)
where the body is the code to emit the HTML of the page. It’ll be wrapped in a call to FOO’s html
macro, so for simple pages it might contain nothing but s-expression HTML.
Within the body, the query parameter variables will be bound to values of query parameters with the same name or from a cookie. In the simplest case, a query parameter’s value will be the string taken from the query parameter or post data field of the same name. If the query parameter is specified with a list, you can also specify an automatic type conversion, a default value, and whether to look for and save the value of the parameter in a cookie. The complete syntax for a query-parameter is as follows:
name | (name type [default-value] [stickiness])
The type must be a name recognized by define-url-function
. I’ll discuss in a moment how to define new types. The default-value must be a value of the given type. Finally, stickiness, if supplied, indicates that the parameter’s value should be taken from an appropriately named cookie if no query parameter is supplied and that a Set-Cookie header should be sent in the response that saves the value in the cookie of the same name. Thus, a sticky parameter, after being explicitly supplied a value via a query parameter, will keep that value on subsequent requests of the page even when no query parameter is supplied.
The name of the cookie used depends on the value of stickiness: with a value of :global
, the cookie will be named the same as the parameter. Thus, different functions that use globally sticky parameters with the same name will share the value. If stickiness is :package
, then the cookie name is constructed from the name of the parameter and the package of the function’s name; this allows functions in the same package to share values but not have to worry about stomping on parameters of functions in other packages. Finally, a parameter with a stickiness value of :local
will use a cookie made from the name of the parameter, the package of the function name, and the function name, making it unique to that function.
For instance, you can use define-url-function
to replace the previous eleven-line definition of random-page
with this five-line version:
(define-url-function random-number (request (limit integer 1000))
(:html
(:head (:title "Random"))
(:body
(:p "Random number: " (:print (random limit))))))
If you wanted the limit argument to be sticky, you could change the limit declaration to (limit integer 1000 :local)
.