Tuesday, 18 December 2007

RESTful handlers with Hunchentoot

I am not quite sure what REST is but I know that following REST practices gives you URLs like http://www.mycompany.com/resources/94182. Pretty isn't it?

Django has a URL dispatcher which allows you to specify regular expressions that match incoming URLs and call a specific handler. If you wanted function resource_page to handle requests to URLs similar to the above, you would specify /resources/\d* as the regular expression. The slashes at the beginning and end of the regular expression are optional. Effectively, you are writing /?resources/\d*/?. This would match:


  • /resources/

  • /resources/123

  • /resources/123/


I'm not entirely sure that it would match /resources// (try it to see!)

This would not match /resources/abcd and other similar URLs.

In Django, the handler is written as:

def resource_page(request,resourceid):
# ....

And the dispatcher is registered like:

urlpatterns = patterns('',
(r"/resources/\d*",resource_page),
)

A little thing I forgot to mention was that if you want the matches to be bound to function arguments, you have to make sure the regex remembers them. In this case, one would really want to write:

urlpatterns = patterns('',
(r"/resources/(\d*)",resource_page),
)


As is my nature, I decided I want this functionality as a Hunchentoot handler. Hunchentoot works pretty much the same way except you only specify dispatch handlers. When a request comes in, Hunchentoot iterates through the dispatch handlers and if one of them returns a function, that function is called to handle the request otherwise the default handler is called.

So the solution is obvious (I think!): I want to write a handler that matches the requested URL against a regex, binds any matches to function parameters and returns that function.

What I want to write in my code is something like:

;;; I like to be explicit about the slashes myself
(create-regex-dispatcher "^/resources/(\\d*)" #'resource-page)

This is obviously very simple once you have a regex engine. Fortunately, not only has Edi Weitz made a billion other libraries including Hunchentoot, but he has also written one of the fastest regex libraries, surpassing even Perl. Crazy.

Anyway, here is the code:

(defun create-regex-dispatcher (regex page-function)
"Just like tbnl:create-regex-dispatcher except it extracts the matched values
and passes them onto PAGE-FUNCTION as arguments. You want to be explicit about
where the slashes go.

For example, given:
(defun blog-page (pagenum)
... )

(push (create-regex-dispatcher \"^/blog/page(\\d+)\" #'blog-page)
tbnl:*dispatch-table*)

When the url blog/page5 is accessed, blog-page is called with pagenum
set to 5."
(let ((scanner (cl-ppcre:create-scanner regex)))
(lambda (request)
(multiple-value-bind (whole-match matched-registers)
(cl-ppcre:scan-to-strings scanner (tbnl:script-name request))
(when whole-match
(lambda ()
(apply page-function (coerce matched-registers 'list))))))))

And a simple example for using it:

;;; If there is no match, id will be the empty string.
;;; Should be a better way to handle this, but OK for now.
(defun resource-function (&optional id)
(cl-who:with-html-output-to-string(s)
(:html
(:body
(tbnl:log-message* "~A ~A")
(if (or (null id)
(= 0 (length id)))
(cl-who:htm
(cl-who:str "No resource"))
(cl-who:htm
(cl-who:fmt "Resource ~A" id)))))))

(push (create-regex-dispatcher "^/resources/(\\d*)" #'resource-function)
tbnl:*dispatch-table*)

8 comments:

Matt said...

Don't have a release yet, but I've been working on a package that provides nicer syntax for building web apps.

Working example here:
http://paste.factorcode.org/responder/pastebin/show-paste?n=47

Sohail Somani said...

Nicely done! Are you using Hunchentoot for fun or profit? :-)

Matt said...

Thanks, that syntax is inspired by web.py. There's more to come, I'd like to provide more helper macros for working with clsql, cl-emb, and cl-who.

For fun that will hopefully turn to profit. =)

Sohail Somani said...

Ah, the best kind of fun. You should write your app first and release the package second! Be sure to let me know what it is :-)

mihai turcu said...

Thanks, you just saved my day :)
I came to hunchentoot after 2+ years of Django, and being used with how Django treats URLs and URL parameters I was looking for something similar. This was just it :)

Ala'a [cmo-0] said...

Thanks i was looking for a similar thing!

regarding the last pattern /resources//

the following emacs regexp (figuerout how to translate ;) ) will only match the first three patterns
/resources/
/resources/123
/resources/123/

"/?resources/\\([[:digit:]]+/?\\)?$"

thanks again

cmo-0

Kushal Das said...

I am trying out the example, but the id is coming always null :( Any clue what I am doing wrong ?

Sohail Somani said...

Kushal, that probably depends on your regex and the URL you are retrieving.