Hunchentoot dispatch by HTTP method
Asked Answered
C

3

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)))
Corley answered 27/9, 2013 at 23:50 Comment(2)
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.Cellulose
Thanks for looking into the source for me :) I guess I'll have to implement something on my own for itCorley
S
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.

Singlecross answered 18/10, 2014 at 13:8 Comment(0)
D
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
  )
Deadfall answered 11/10, 2013 at 16:28 Comment(1)
Thanks for the pointers -- I've looked into using Caveman, but switched back to Hunchentoot after this issueCorley
T
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))
Translate answered 16/10, 2019 at 10:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.