On 7/19/07, Robert Uhl eadmund42@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) ,@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
"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.htm...
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