Я сегодня рассматривал
Clack и должен сказать, что мне это определённо не нравится. Впрочем WSGI у меня тоже никогда больших восторгов не вызывал. Слишком универсальный протокол и слишком слабый API. Но, сама концепция middleware (в том виде, как она есть, например, в Pylons) не так уж и плоха. Просто, на мой вкус, её надо сделать более конкретной. Кое какие мысли у меня на эту тему уже крутились и я даже
показывал недавно, как можно тонко влиять на процесс обработки запроса. И вот сегодня идея начала приобретать конкретную форму. В частности, изменился макрос restas:mount-submodule, теперь можно писать примерно так:
(restas:mount-submodule -mysubmodule- (#:mymodule middleware1 middleawre2)
..)
Здесь middleware1 и middleware2 это функции, которые должны принимать маршрут и возвращать другой.
В момент построения дерева маршрутов (а это происходит каждый раз при вызове функции restas:recconect-all-routes) для конкретного субмодуля строиться список обрабатываемых им маршрутов и пропускается через цепочку middleware-вызовов. middleware-функция может как угодно изменить маршрут или вообще вернуть другой. Специально для поддержки этого я добавил в cl-routes новый класс routes:proxy-route (который реализует известный паттерн proxy).
Скажем, с помощью модуля restas-directory-publisher можно публиковать директории со статикой:
(restas:mount-submodule -tmp- (#:restas.directory-publisher)
(restas.directory-publisher:*baseurl* '("tmp"))
(restas.directory-publisher:*directory* #P"/tmp/)
(restas.directory-publisher:*autoindex* t))
Такой код позволит любому просматривать содержимое директории /tmp на сервере, но кто знает что там может быть. Теперь с помощью middleware можно защитить содержимое этого каталога, требуя от посетителей пройти HTTP-авторизацию:
(defclass http-auth-route (routes:proxy-route) () )
(defmethod routes:route-check-conditions ((route http-auth-route) bindings)
(if (call-next-method)
(multiple-value-bind (user password) (hunchentoot:authorization)
(or (and (string= user "hello")
(string= password "world"))
(hunchentoot:require-authorization)))))
(defun http-auth-middleware (route)
(make-instance 'http-auth-route :target route))
(restas:mount-submodule -tmp- (#:restas.directory-publisher http-auth-middleware)
(restas.directory-publisher:*baseurl* '("tmp"))
(restas.directory-publisher:*directory* #P"/tmp/)
(restas.directory-publisher:*autoindex* t))
Здесь определяется новый класс http-auth-route, наследующий от routes:proxy-route, и для него специализируется метод routes:route-check-conditions, который вызывается для проверки соответствия маршрута условиям запроса. Если маршрут проходит все проверки, то проверяется прошёл ли пользователь HTTP-авторизацию. Функция http-auth-middleware используется для создания таких маршрутов и указывается в списке middlewares макроса restas:mount-submodule.
Для маршрутов, наследующих от routes:proxy-route, имеет смысл определять специализации методов routes:route-check-conditions и/или restas:process-route.
Чуть более сложный пример использования описанных возможностей можно найти
здесь.