6

I couldn't find any documentation on how to dispatch based on HTTP method (on the same uri). The closest I got was :default-request-type on the define-easy-handler -- but it seems to dispatch to the latter, even though I use GET method:

(define-easy-handler (index :uri "/" :default-request-type :get) ()
  (log-message* :info "GET on index ------ ")
  (format nil "Hello World"))

(define-easy-handler (echo :uri "/" :default-request-type :post) ()
  (log-message* :info "POST on index ------ ")
  (format nil "~S" (raw-post-data :force-text t)))
mck
  • 1,334
  • 2
  • 12
  • 20
  • 1
    From looking at the source code it would seem that `default-requiest-type` affects only what "arguments" are considered when invoking the handler. It doesn't affect whether the handler will be called. So it seems like you are on your own implementing that. –  Sep 28 '13 at 06:49
  • Thanks for looking into the source for me :) I guess I'll have to implement something on my own for it – mck Sep 30 '13 at 00:11

3 Answers3

4

The (perhaps slightly deceptively named) :uri parameter is allowed to be either a string or a predicate on the request object. So, you can pass a function there that checks if the method and path match. I wrote a macro to make it prettier:

(defmacro method-path (methods path)
  "Expands to a predicate the returns true of the Hunchtoot request
has a SCRIPT-NAME matching the PATH and METHOD in the list of METHODS.
You may pass a single method as a designator for the list containing
only that method."
  (declare
   (type (or keyword list) methods)
   (type string path))
  `(lambda (request)
     (and (member (hunchentoot:request-method* request)
                 ,(if (keywordp methods)
                      `'(,methods)
                      `',methods))
          (string= (hunchentoot:script-name* request)
                   ,path))))

(hunchentoot:define-easy-handler (get-handler :uri (method-path :get "/hello")) ()
  "hello!")

(hunchentoot:define-easy-handler (post-handler :uri (method-path (:post :put) "/hello")) ()
  "a post or a put!")

In the case where the path is found but the method isn't, we should probably return an HTTP 405 error instead of the 404 error that Hunchentoot returns when no handlers match. In order to do this, you could manually write a catch-all handler for every path you define. The 405 response is supposed to include a list of acceptable methods, and I can't think of an easy way to generate one short of modifying define-easy-handler to support specialization on method directly, which might be a good idea.

Samuel Edwin Ward
  • 6,526
  • 3
  • 34
  • 62
2

Many frameworks built on top of hunchentoot have that. Restas and Caveman are just two examples. For example in Restas you can say:

(restas:define-route foo ("/foo" :method :get)
  ; some code here
  )

(restas:define-route foo/post ("/foo" :method :post)
  ; some other code here
  )
Pavel Penev
  • 659
  • 5
  • 18
  • Thanks for the pointers -- I've looked into using Caveman, but switched back to Hunchentoot after this [issue](https://github.com/xach/buildapp/issues/13) – mck Oct 11 '13 at 21:56
0

We now have an Hunchentoot add-on to do just that: easy-routes. It brings dispatch by HTTP method, arguments extraction from the url path, and a handy decorator notation.

To use it, we just have to use its routes-acceptor instead of the default easy-acceptor:

(hunchentoot:start (make-instance 'easy-routes:routes-acceptor))

An example:

(defroute foo ("/foo/:arg1/:arg2" :method :get)
   (&get w)
    (format nil "<h1>FOO arg1: ~a arg2: ~a ~a</h1>" arg1 arg2 w))
Ehvince
  • 17,274
  • 7
  • 58
  • 79