The Implementation

I’ll explain the implementation of define-url-function from the top down. The macro itself looks like this:

  1. (defmacro define-url-function (name (request &rest params) &body body)
  2. (with-gensyms (entity)
  3. (let ((params (mapcar #'normalize-param params)))
  4. `(progn
  5. (defun ,name (,request ,entity)
  6. (with-http-response (,request ,entity :content-type "text/html")
  7. (let* (,@(param-bindings name request params))
  8. ,@(set-cookies-code name request params)
  9. (with-http-body (,request ,entity)
  10. (with-html-output ((request-reply-stream ,request))
  11. (html ,@body))))))
  12. (publish :path ,(format nil "/~(~a~)" name) :function ',name)))))

Let’s take it bit by bit, starting with the first few lines.

  1. (defmacro define-url-function (name (request &rest params) &body body)
  2. (with-gensyms (entity)
  3. (let ((params (mapcar #'normalize-param params)))

Up to here you’re just getting ready to generate code. You **GENSYM** a symbol to use later as the name of the entity parameter in the **DEFUN**. Then you normalize the parameters, converting plain symbols to list form using this function:

  1. (defun normalize-param (param)
  2. (etypecase param
  3. (list param)
  4. (symbol `(,param string nil nil))))

In other words, declaring a parameter with just a symbol is the same as declaring a nonsticky, string parameter with no default value.

Then comes the **PROGN**. You must expand into a **PROGN** because you need to generate code to do two things: define a function with **DEFUN** and call publish. You should define the function first so if there’s an error in the definition, the function won’t be published. The first two lines of the **DEFUN** are just boilerplate.

  1. (defun ,name (,request ,entity)
  2. (with-http-response (,request ,entity :content-type "text/html")

Now you do the real work. The following two lines generate the bindings for the parameters specified in define-url-function other than request and the code that calls set-cookie-header for the sticky parameters. Of course, the real work is done by helper functions that you’ll look at in a moment.12

  1. (let* (,@(param-bindings name request params))
  2. ,@(set-cookies-code name request params)

The rest is just more boilerplate, putting the body from the define-url-function definition in the appropriate context of with-http-body, with-html-output, and html macros. Then comes the call to publish.

  1. (publish :path ,(format nil "/~(~a~)" name) :function ',name)

The expression (format nil "/~(~a~)" name) is evaluated at macro expansion time, generating a string consisting of /, followed by an all-lowercase version of the name of the function you’re about to define. That string becomes the :path argument to publish, while the function name is interpolated as the :function argument.

Now let’s look at the helper functions used to generate the **DEFUN** form. To generate parameter bindings, you need to loop over the params and collect a snippet of code for each one, generated by param-binding. That snippet will be a list containing the name of the variable to bind and the code that will compute the value of that variable. The exact form of code used to compute the value will depend on the type of the parameter, whether it’s sticky, and the default value, if any. Because you already normalized the params, you can use **DESTRUCTURING-BIND** to take them apart in param-binding.

  1. (defun param-bindings (function-name request params)
  2. (loop for param in params
  3. collect (param-binding function-name request param)))
  4. (defun param-binding (function-name request param)
  5. (destructuring-bind (name type &optional default sticky) param
  6. (let ((query-name (symbol->query-name name))
  7. (cookie-name (symbol->cookie-name function-name name sticky)))
  8. `(,name (or
  9. (string->type ',type (request-query-value ,query-name ,request))
  10. ,@(if cookie-name
  11. (list `(string->type ',type (get-cookie-value ,request ,cookie-name))))
  12. ,default)))))

The function string->type, which you use to convert strings obtained from the query parameters and cookies to the desired type, is a generic function with the following signature:

  1. (defgeneric string->type (type value))

To make a particular name usable as a type name for a query parameter, you just need to define a method on string->type. You’ll need to define at least a method specialized on the symbol string since that’s the default type. Of course, that’s pretty easy. Since browsers sometimes submit forms with empty strings to indicate no value was supplied for a particular value, you’ll want to convert an empty string to **NIL** as this method does:

  1. (defmethod string->type ((type (eql 'string)) value)
  2. (and (plusp (length value)) value))

You can add conversions for other types needed by your application. For instance, to make integer usable as a query parameter type so you can handle the limit parameter of random-page, you might define this method:

  1. (defmethod string->type ((type (eql 'integer)) value)
  2. (parse-integer (or value "") :junk-allowed t))

Another helper function used in the code generated by param-binding is get-cookie-value, which is just a bit of sugar around the get-cookie-values function provided by AllegroServe. It looks like this:

  1. (defun get-cookie-value (request name)
  2. (cdr (assoc name (get-cookie-values request) :test #'string=)))

The functions that compute the query parameter and cookies names are similarly straightforward.

  1. (defun symbol->query-name (sym)
  2. (string-downcase sym))
  3. (defun symbol->cookie-name (function-name sym sticky)
  4. (let ((package-name (package-name (symbol-package function-name))))
  5. (when sticky
  6. (ecase sticky
  7. (:global
  8. (string-downcase sym))
  9. (:package
  10. (format nil "~(~a:~a~)" package-name sym))
  11. (:local
  12. (format nil "~(~a:~a:~a~)" package-name function-name sym))))))

To generate the code that sets cookies for sticky parameters, you again loop over the list of parameters, this time collecting a snippet of code for each sticky param. You can use the when and collect it **LOOP** forms to collect only the non-**NIL** values returned by set-cookie-code.

  1. (defun set-cookies-code (function-name request params)
  2. (loop for param in params
  3. when (set-cookie-code function-name request param) collect it))
  4. (defun set-cookie-code (function-name request param)
  5. (destructuring-bind (name type &optional default sticky) param
  6. (declare (ignore type default))
  7. (if sticky
  8. `(when ,name
  9. (set-cookie-header
  10. ,request
  11. :name ,(symbol->cookie-name function-name name sticky)
  12. :value (princ-to-string ,name))))))

One of the advantages of defining macros in terms of helper functions like this is that it’s easy to make sure the individual bits of code you’re generating look right. For instance, you can check that the following set-cookie-code:

  1. (set-cookie-code 'foo 'request '(x integer 20 :local))

generates something like this:

  1. (WHEN X
  2. (SET-COOKIE-HEADER REQUEST
  3. :NAME "com.gigamonkeys.web:foo:x"
  4. :VALUE (PRINC-TO-STRING X)))

Assuming this code will occur in a context where x is the name of a variable, this looks good.

Once again, macros have allowed you to distill the code you need to write down to its essence—in this case, the data you want to extract from the request and the HTML you want to generate. That said, this framework isn’t meant to be the be-all and end-all of Web application frameworks—it’s just a little sugar to make it a bit easier to write simple apps like the one you’ll write in Chapter 29.

But before you can get to that, you need to write the guts of the application for which the Chapter 29 application will be the user interface. You’ll start in the next chapter with a souped-up version of the database you wrote in Chapter 3, this time to keep track of ID3 data extracted from MP3 files.


1Readers new to Web programming will probably need to supplement this introduction with a more in-depth tutorial or two. You can find a good set of online tutorials at http://www.jmarshall.com/easy/.

2Loading a single Web page may actually involve multiple requests—to render the HTML of a page containing inline images, the browser must request each image individually and then insert each into the appropriate place in the rendered HTML.

3Much of the complexity around Web programming is a result of trying to work around this fundamental limitation in order to provide a user experience that’s more like the interactivity provided by desktop applications.

4Unfortunately, dynamic is somewhat overloaded in the Web world. The phrase Dynamic HTML refers to HTML containing embedded code, usually in the language JavaScript, that can be executed in the browser without further communication with the Web server. Used with some discretion, Dynamic HTML can improve the usability of a Web-based application since, even with high-speed Internet connections, making a request to a Web server, receiving the response, and rendering the new page can take a noticeable amount of time. To further confuse things, dynamically generated pages (in other words, generated on the server) could also contain Dynamic HTML (code to be run on the client.) For the purposes of this book, you’ll stick to dynamically generating plain old nondynamic HTML.

5http://www.fractalconcept.com/asp/html/mod_lisp.html

6http://lisplets.sourceforge.net/

7AllegroServe also provides a framework called Webactions that’s analogous to JSPs in the Java world—instead of writing code that generates HTML, with Webactions you write pages that are essentially HTML with a bit of magic foo that turns into code to be run when the page is served. I won’t cover Webactions in this book.

8Loading PortableAllegroServe will create some other packages for the compatibility libraries, but the packages you’ll care about are those three.

9The ~@ followed by a newline tells **FORMAT** to ignore whitespace after the newline, which allows you to indent your code nicely without adding a bunch of whitespace to the HTML. Since white-space is typically not significant in HTML, this doesn’t matter to the browser, but it makes the generated HTML source look a bit nicer to humans.

10FOO is a recursive tautological acronym for FOO Outputs Output.

11For information about the meaning of the other parameters, see the AllegroServe documentation and RFC 2109, which describes the cookie mechanism.

12You need to use **LET*** rather than a **LET** to allow the default value forms for parameters to refer to parameters that appear earlier in the parameter list. For example, you could write this:

  1. (define-url-function (request (x integer 10) (y integer (* 2 x))) ...)

and the value of y, if not explicitly supplied, would be twice the value of x.