Hunchentoot is a web server and not a heavy weight web development framework. It provides methods to implement URL paths for static files, directory paths and with regex’s, very much like the mainstream web servers. It does not provide an easy way to define routes for a REST API. When you use Hunchentoot without a web development framework you will probably want something to make route definition easier.
There are a few options for building REST APIs available in frameworks, Hunchentoot derivatives or other web servers but I wanted to implement REST routes with the original Hunchentoot web server. I found three libraries that can do this: simple-routes, Froute and easy-routes.
Simple-routes is the simplest and easiest to use. Routes are defined in a list
similar to Hunchentoot’s *dispatch-table*
. It supports variables in the URL
segments but there is no support for middleware1 type functionality.
Froute is the most powerful of the three. It is based on CLOS and is designed so it can be used with any web server although only a Hunchentoot connector is currently implemented. Routes are defined as CLOS classes and even though middleware is not a specific feature the documentation gives an example on how to use class inheritance to implement such functionality. The power of being CLOS based also makes this library the most complex to use.
Easy-routes has the concept of decorators which are functions that execute before the route body so it can be used to implement middleware functionality. Unlike Clack’s middleware which are defined at a central place for all routes, decorators need to be applied to each route handler individually. It’s not quite there, but close enough.
The lack of middleware options disqualified simple-routes for me and Froute looked like it provides everything I need, and more, but with much greater complexity than easy-routes. I decided to use easy-routes with the option to switch to Froute when I needed the extra capability.
Hunchentoot takes an “acceptor” argument at startup. Easy-routes provides two
options: easy-routes-acceptor
and routes-acceptor
. Easy-routes-acceptor
first executes all the route handlers and if no suitable handler is found it
executes the normal Hunchentoot request handlers. The routes-acceptor
executes only the route handlers and returns an 404 NOT FOUND
error if no
suitable handler is found.
I use routes-acceptor
because it ensures that only requests with explicitly
defined handlers are handled. With the easy-routes-acceptor
it is too easy to
create a security hole with some default Hunchentoot request handler that
catches non-existent routes. It can be burdensome to use this approach for
handling static files but I run Hunchentoot behind Nginx which also handles the
static files.
The table summarises the main features I investigated:
simple-routes | easy-routes | Froute | de.setf.http | |
---|---|---|---|---|
Web server | Hunchentoot | Hunchentoot | Hunchentoot (can be expanded to others) | Hunchentoot |
REST routes | Yes | Yes | Yes | Yes |
Argument extraction from URL | Yes | Yes | Yes | Yes |
Dispatch based on HTTP method | Yes | Yes | Yes | Yes |
Middleware | No | Decorators | CLOS inheritance | Yes |
Fallback for undefined routes | Hunchentoot easy-handler framework | None or Hunchentoot easy-handler framework | None | None |
Learning curve | Negligible | Minimal | Medium. Requires some CLOS knowledge. | High. Requires CLOS knowledge. |
Addendum
James Anderson introduced me to de.setf.http after the post’s initial publication. de.setf.http is based on CLOS and it seems to be at least as powerful as Froute, if not more.
The library implements HTTP processing using composable functions. While it does not have an explicit concept of middleware, obtaining middleware-like functionality forms a natural part of the request handlers.
The only documentation is the README file and two sparsely commented examples. This library works very different than most others and relies heavily on CLOS. The novel approach and the lack of documentation make for a steep learning curve which may be worthwhile if you like the library’s approach.
-
Middleware are functions that run before or after the main request handler. They are called as part of the request handling process and not by the handler. This makes them ideal to handle general functions like setting up a database connection, performing authorisation or any task which is not part of a particular request handler. ↩