[hunchentoot-devel] Middleware with Hunchentoot

Mac Chan emailmac at gmail.com
Fri Jul 20 06:25:19 UTC 2007


On 7/19/07, Robert Uhl <eadmund42 at gmail.com> wrote:
> >From Python's WSGI I've gotten the idea of middleware--that is, stuff
> which runs around user-defined code in some user-defined order.  A
> null-effect middleware function might be defined like this:

No offense but I find it amusing that people using other languages
always need to invent new design pattern `terms' to describe something
that is just common sense.

Just earlier in c.l.l I learned something new called "front
controller" and spent 10 minutes reading a website with fancy graphs
that describes what a "front controller" is. That turned out to be a
big waste of time because it is nothing new (and of course hunchentoot
already have it built in).

> One can see how a middleware function could set things up for the
> user-defined handler, could decide whether or not to call the user's
> handler and could override whatever that handler returns.
>
> A convenience macro def-middleware might make it possible to write the
> null-effect function like this:
>
>   (def-middleware null-effect (request)
>     (call-next-middleware))
>
> And the user might be able to create a dispatcher thus:
>
>   (create-regex-dispatcher "/foo/bar?baz" (null-effect #'my-handler))

Sorry but your null-effect example doesn't really show the benefit of
using this middleware wrapper. I can't really tell why I'd want to do
something like this.

> Obviously middleware could be chained:
>
>   (create-regex-dispatcher "/foo/bar?baz"
>                            (authentication (caching #'my-handler)))

I'm really confused here. I assume that both authentication and
caching are macros, right?

If they are functions, then what do they do? what does the middleware
framework do? and what all these buy us?

If they are macros, then why not just define them straightforwardly?

(defmacro with-authentication (&body body)
  `(progn
    (require-authorization)
    , at body))


> Question: does this seem like a reasonable way to handle middleware,
> particularly authentication/authorisation mechanisms?  I'm fairly new to
> Lisp, but it _seems_ like it's a fairly Lispy way to go about things.

Assuming that you've already gone through the extensive documentation,
I think your not understanding the Hunchentoot code might be the
problem. It actually already does 90% of the work for you.

Hunchentoot is very well written and easy to read. If you are using
slime, just open the test.lisp and go through the example.lisp.
Whenever you see some functions / macros that might look non-trival to
you, press Meta-. to jump to the source and see how it's implemented.

For instance

(defun authorization-page ()
  (multiple-value-bind (user password)
      (authorization)
    (cond ((and (equal user "nanook")
                (equal password "igloo"))
           (with-html
             (:html
              (:head (:title "Hunchentoot page with Basic Authentication"))
              (:body
               (:h2 (hunchentoot-link)
                " page with Basic Authentication")
               (info-table (header-in "Authorization")
                           (authorization))))))
          (t
           (require-authorization)))))

what does (require-authorization) do ?

(defun require-authorization (&optional (realm "Hunchentoot"))
  "Sends back appropriate headers to require basic HTTP authentication
(see RFC 2617) for the realm REALM."
  (setf (header-out "WWW-Authenticate")
          (format nil "Basic realm=\"~A\"" (quote-string realm))
        (return-code *reply*)
          +http-authorization-required+)
  (throw 'handler-done nil))

So it seems like that it will (throw 'handler-done nil) when authorization fails

>From http://weitz.de/hunchentoot/#handler-done

"This is a catch tag which names a catch which is active during the
lifetime of a handler. The handler can at any time throw the outgoing
content body (or NIL) to this catch to immediately abort handling the
request. See the source code of REDIRECT for an example."

With this knowledge you can see that to password protect a page you
can just simply insert a call to require-authorization at the begining
of your function body. No design pattern required.

(define-easy-handler (my-url-handler :uri "/my-url") ()
  (require-authorization)
  (with-html (:h1 "hi"))

Similarly you can define your session checking routine the same way.

(defun ensure-login ()
  (unless (session-value :user)
    (redirect "/login")))

(define-easy-handler (my-url-handler :uri "/my-url") ()
  (ensure-login)
  (with-html (:h1 "hi"))

The caching functionality would probably require a macro that wrap
around define-easy-handler. Using the uri as a key to a hashtable, you
can first check if it is already generated (assuming your html page
generation is time consuming and complicated) and return right away.

It could be a good exercise to try your hands on.

BTW, before Edi introduced define-easy-handler in hunchentoot, I used
to have my own version of allegro's "Extended Maps" running on tbnl.

http://opensource.franz.com/aserve/aserve-dist/webactions/doc/webactions.html#Webactions

It is very similar to what you tried to describe above, and very easy
to work with.

However, let me tell you a little secret -  define-easy-handler is
probably all you ever need :-)

Anyway, happy hacking!

Regards,
-- Mac



More information about the Tbnl-devel mailing list