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:
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:
And the dispatcher is registered like:
urlpatterns = patterns('',
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('',
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)
When the url blog/page5 is accessed, blog-page is called with pagenum
set to 5."
(let ((scanner (cl-ppcre:create-scanner regex)))
(multiple-value-bind (whole-match matched-registers)
(cl-ppcre:scan-to-strings scanner (tbnl:script-name request))
(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)
(tbnl:log-message* "~A ~A")
(if (or (null id)
(= 0 (length id)))
(cl-who:str "No resource"))
(cl-who:fmt "Resource ~A" id)))))))
(push (create-regex-dispatcher "^/resources/(\\d*)" #'resource-function)