Search code examples
common-lisphunchentoot

Hunchentoot dispatcher


I'm relatively new to Common Lisp (SBCL) and Hunchentoot (using Quicklisp). Can someone tell me how I can get this to work? I'm trying to wrap an Hunchentoot server and some paths in a function as a unit. When I run this, only Hunchentoot's index page is available, the paths /a and /b aren't.

(defun app0 (port)
  (let ((*dispatch-table* nil) (server (make-instance 'hunchentoot:acceptor :port port)))
    (push (hunchentoot:create-prefix-dispatcher "/a" (lambda () "a")) *dispatch-table*)
    (push (hunchentoot:create-prefix-dispatcher "/b" (lambda () "b")) *dispatch-table*)
    (hunchentoot:start server) server))

Solution

  • There are multiple problems, as far as I can see. First, request handling via *dispatch-table* requires, that the acceptor is of type easy-acceptor, i.e., you will have to

    (make-instance 'easy-acceptor ...)
    

    The documentation has the details.

    The second problem is, that you rebind the *dispatch-table* during the set-up code, and push new values into this binding. Since the binding is reverted after the let is finished (and since hunchentoot:start works asynchronously), your entries in the *dispatch-table* are effectively lost, when the server is running. Try

    (push (hunchentoot:create-prefix-dispatcher "/a" (lambda () "a")) *dispatch-table*)
    (push (hunchentoot:create-prefix-dispatcher "/b" (lambda () "b")) *dispatch-table*)
    

    at the top-level (or do something like that in a dedicated set-up function). If you don't like the global *dispatch-table* approach, you can also create a subclass of acceptor, and override acceptor-dispatch-request (and thus, implement any kind of dispatch you like).

    Just as a side-note: you do not prefix *dispatch-table*, while you prefix virtually any other symbol from hunchentoot's package. Is this just a copy/paste mistake, or is this also the case in your actual code? If you do not :use the hunchentoot package in wherever package your code happens to live, then you'd also have to qualify the dispatch table as hunchentoot:*dispatch-table*.

    Edit (to address the question in the comment section) There is an example in the hunchentoot documentation, which seems to do exactly what you want to do:

    (defclass vhost (tbnl:acceptor)
      ((dispatch-table
        :initform '()
        :accessor dispatch-table
        :documentation "List of dispatch functions"))
      (:default-initargs  
       :address "127.0.0.1"))
    
    (defmethod tbnl:acceptor-dispatch-request ((vhost vhost) request)
      (mapc (lambda (dispatcher)
          (let ((handler (funcall dispatcher request)))
            (when handler
              (return-from tbnl:acceptor-dispatch-request (funcall handler)))))
        (dispatch-table vhost))
      (call-next-method))
    
    (defvar vhost1 (make-instance 'vhost :port 50001))
    (defvar vhost2 (make-instance 'vhost :port 50002))
    
    (push
     (tbnl:create-prefix-dispatcher "/foo" 'foo1)
     (dispatch-table vhost1))
    (push
     (tbnl:create-prefix-dispatcher "/foo" 'foo2)
     (dispatch-table vhost2))
    
    (defun foo1 () "Hello")
    (defun foo2 () "Goodbye")
    
    (tbnl:start vhost1)
    (tbnl:start vhost2)
    

    (comments present in the documentation removed for brevity). The tbnl is a predefined nickname for package hunchentoot. You can use both interchangeably, though I would recommend, that you pick one and stick to it. Mixing both might generate confusion.