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:
(defun null-effect (request next) (when next (funcall next request)))
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))
Obviously middleware could be chained:
(create-regex-dispatcher "/foo/bar?baz" (authentication (caching #'my-handler)))
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.
Thoughts?
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
(defmacro with-authentication (&body body) `(progn (require-authorization) ,@body))
Ah, I forget to reread my email before I click send.
It should be something like this:
(defun do-authorization () (multiple-value-bind (user password) (authorization) (unless (and (equal user "nanook") (equal password "igloo")) (require-authorization))))
(defmacro with-authentication (&body body) `(progn (do-authorization) ,@body))
(define-easy-handler (my-url-handler :uri "/my-url") () (require-authorization) (with-html (:h1 "hi"))
Similarly the above is wrong.
(define-easy-handler (my-url-handler :uri "/my-url") () (do-authorization) (with-html (:h1 "hi"))
BTW none of the code are tested, but you get the idea ...
Regards, -- Mac
Why that fancy, complicated and unessesary machinery ?
I do authentication much simpler with hunchentoot dispatch. Remember, dispatch can pass request further, if it returns nil.
(setq *dispatch-table* (nconc (mapcar (lambda (x) (create-folder-dispatcher-and-handler (conc "/" x) (conc *app-path* "www/" x))) '("js/" "css/" "images/" "swf/"))
(prefix-dispatchers '(("/app" login-swf) ("/login.json" login-json)))
(list #'check-if-logged-in)
(list 'dispatch-easy-handlers #'default-dispatcher)))
So first you list open resources that do not need authentication. Then goes your function that checks authentication: (check-if-logged-in), and then all other pages that require authentication.
-----Original Message----- From: tbnl-devel-bounces@common-lisp.net [mailto:tbnl-devel-bounces@common-lisp.net] On Behalf Of Mac Chan Sent: Thursday, July 19, 2007 11:34 PM To: General interest list for Hunchentoot and CL-WEBDAV Subject: Re: [hunchentoot-devel] Middleware with Hunchentoot
(defmacro with-authentication (&body body) `(progn (require-authorization) ,@body))
Ah, I forget to reread my email before I click send.
It should be something like this:
(defun do-authorization () (multiple-value-bind (user password) (authorization) (unless (and (equal user "nanook") (equal password "igloo")) (require-authorization))))
(defmacro with-authentication (&body body) `(progn (do-authorization) ,@body))
(define-easy-handler (my-url-handler :uri "/my-url") () (require-authorization) (with-html (:h1 "hi"))
Similarly the above is wrong.
(define-easy-handler (my-url-handler :uri "/my-url") () (do-authorization) (with-html (:h1 "hi"))
BTW none of the code are tested, but you get the idea ...
Regards, -- Mac _______________________________________________ tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
"Mac Chan" emailmac@gmail.com writes:
(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.
Here's a more realistic example:
(def-middleware authentication (request) (if (apply #'authenticatedp (multiple-value-list (authorization))) (call-next-middleware) (require-authentication)))
Then the user can force a page to use authentication with:
(create-regex-dispatcher "/foo/bar" (authentication #'my-handler))
Instead of having to write any code inside MY-HANDLER.
I've not fully fleshed out the idea yet, of course.
Ideally, it'd be possible to specify a single stack of middleware to use on the _entire_ Hunchentoot server, but I've not yet figured out where to do that.
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?
Nope, just functions created by DEF-MIDDLEWARE. Something like:
(defun authentication (handler-or-middleware) (lambda (request) (if (apply #'authenticatedp (multiple-value-list (authorization))) (when (handler-or-middleware) (funcall handler-or-middleware request)) (require-authentication))))
That is, they're just functions which create handler wrappers.
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))
Because having to write:
(defun display-foo-page (request) (with-authentication (with-caching (with-etags (with-gzip (do-stuff))))))
Gets unwieldy.
But on reflection you're right: handler-specific code is probably best expressed as a WITH- macro. Normally one would probably be doing something like:
(defun display-foo-page (request) (with-authorisation (maintainer editor) (do-stuff)))
Without more than a single macro. The other stuff I was thinking of doing (caching, URL cleaning, gzipping) should really be run around _all_ handlers, not just a few. I don't think Hunchentoot exposes a good place for this kind of thing. URL cleaning is easy enough: put it at the beginning of *dispatch-table* where it'll get called early on and can rewrite (request-uri). But where would one put gzipping? It has to do its magic _after_ any handler runs, and process the handler's output. It'd be nicest to specify it once, and not in every single handler's code.
On Fri, 20 Jul 2007 09:24:22 -0600, Robert Uhl eadmund42@gmail.com wrote:
I don't think Hunchentoot exposes a good place for this kind of thing. URL cleaning is easy enough: put it at the beginning of *dispatch-table* where it'll get called early on and can rewrite (request-uri). But where would one put gzipping? It has to do its magic _after_ any handler runs, and process the handler's output. It'd be nicest to specify it once, and not in every single handler's code.
Edi Weitz edi@agharta.de writes:
I don't think Hunchentoot exposes a good place for this kind of thing. URL cleaning is easy enough: put it at the beginning of *dispatch-table* where it'll get called early on and can rewrite (request-uri). But where would one put gzipping? It has to do its magic _after_ any handler runs, and process the handler's output. It'd be nicest to specify it once, and not in every single handler's code.
I imagine the suggested usage is something like this?
(defclass my-dispatch-table () ((table :accessor table)))
(defmethod dispatch-request ((table my-dispatch-table)) (with-cleverness (dispatch-request (table table))))
Cool! Thanks much.
On Fri, 20 Jul 2007 14:41:29 -0600, Robert Uhl eadmund42@gmail.com wrote:
I imagine the suggested usage is something like this?
(defclass my-dispatch-table () ((table :accessor table)))
(defmethod dispatch-request ((table my-dispatch-table)) (with-cleverness (dispatch-request (table table))))
Or just write an :AROUND method around the existing one.
On 7/20/07, Robert Uhl eadmund42@gmail.com wrote:
Then the user can force a page to use authentication with:
(create-regex-dispatcher "/foo/bar" (authentication #'my-handler))
Instead of having to write any code inside MY-HANDLER.
If that's what you want (not having to write any code inside handlers), and if all your handlers that require the same checking share a common uri prefix (like everything under "/admin/..." will require login, then you can probably just use Vagif's suggestion.
In my case I have no more than 20 define-easy-handlers so it was very easy to manage by just adding a simple function call when needed (and they don't need to share a common uri prefix).
Because having to write:
(defun display-foo-page (request) (with-authentication (with-caching (with-etags (with-gzip (do-stuff))))))
Gets unwieldy.
But on reflection you're right: handler-specific code is probably best expressed as a WITH- macro. Normally one would probably be doing something like:
(defun display-foo-page (request) (with-authorisation (maintainer editor) (do-stuff)))
(defun display-foo-page (request) (with-authentication (with-caching (with-etags (with-gzip (do-stuff))))))
Exactly. The with-xxx macros are abstraction and you can also use it indirectly. That's the beauty of macro. Once you have a set of these simple building blocks you can easier group them together. Here's what I'd typically do
(defmacro with-admin-template (&body body) `(with-authentication (with-caching (with-etags (with-gzip (with-html (:html (:head ...) (:admin-header-html ...) ,@body (:admin-footer-html ...))))))))
Notice there is a top level hunchentoot dispatch table, and then define-easy-handler is _another_ level of dispatch table.
You can probably build another dispatch table for each uri prefix like you suggested easily. Have you looked at webaction's documentation? Is that what you want?
BTW, please excuse my pompous reply earlier. It was early in the morning and I was feeling cranky.
Regards, -- Mac
"Mac Chan" emailmac@gmail.com writes:
Have you looked at webaction's documentation? Is that what you want?
Yeah, I used to play around with webactions, but I ended up deciding that it's not quite my cuppa tea. It's pretty cool, but it's way of doing things doesn't quite sync with my own.
At work I'm a big Django user, and I'm playing with creating a Django-like framework for Lisp using Hunchentoot as the server.
BTW, please excuse my pompous reply earlier. It was early in the morning and I was feeling cranky.
Oh, no worries. Thanks for the ideas; you helped me rethink things.
Thoughts?
Like others have said, this might not be the best example since it is so easy to do this in hunchentoot proper. However, don't be discouraged! I think that there is plenty of interesting work to be done here. For example, a widget based framework that sat on top of of hunchentoot would be useful if done properly.
Cheers, Chris Dean
On Thu, Jul 19, 2007 at 10:11:58PM -0600, Robert Uhl 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:
(defun null-effect (request next) (when next (funcall next request)))
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))
Obviously middleware could be chained:
(create-regex-dispatcher "/foo/bar?baz" (authentication (caching #'my-handler)))
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.
Thoughts?
These are called "closures".
Here's a function I use in several places:
(defun call-with-protection (&key username password realm function) "Return a closure that calls FUNCTION if the remote username and password match USERNAME and PASSWORD, or returns an authorization required response otherwise." (lambda () (multiple-value-bind (web-username web-password) (authorization) (if (and web-username web-password (string= web-username username) (string= web-password password)) (funcall function) (require-authorization realm)))))
Here's an example use;
(wf:serve-function (wf:call-with-protection :username "blubba" :password "frink" :realm "roflbot" :function 'recent-page) "/roflbot/recent")
The FUNCTION argument to CALL-WITH-PROTECTION could be another closure generator, and these could be chained as much or as little as you like.
Zach
On 7/20/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:
(defun null-effect (request next) (when next (funcall next request)))
It seems strange to talk about the concept middleware when you have only 1 webframework which will support it. The whole point of WSGI is to make your application capable of using any of the available python webservers, and all such applications and servers being extended in the same way through middleware (code that runs between the server and the application).
So you can have Zope3 application with xslt styling middleware + interactive through the web traceback middleware + twisted server. And you can add as much middleware as you want, change the server, or change the framework used to create the application without having to change the code of the other parts. (this is done by defining the way server passes information to the application and making all the servers conform to that way)
While something like that would be useful to common-lisp as well (being able to have hunchentoot based application run on araneida) or using AllegroServe as a backend for a UCW application. IMHO the place for changes to the code is inside the hunchentoot, AllegroServe and other web frameworks, rather than in the hunchentoot dispatcher.
And if it is only to allow some standartized "plugins" for hunchentoot that can be added to your working application in some easy way, then comparison with WSGI is a bit out of place.
Ignas Mikalajunas