Обслуживание приложений и маршрутов api с различным промежуточным программным обеспечением с использованием Ring и Compojure
У меня есть приложение ring + compojure, и я хочу применить другое промежуточное программное обеспечение в зависимости от того, является ли маршрут частью веб-приложения или частью api (который основан на json).
Я нашел несколько ответов на этот вопрос о переполнении стека и других форумах, но эти ответы кажутся более сложными, чем решение, которое я использовал. Я хотел знать, есть ли недостатки в том, как я это делаю и чего я могу потерять в своем решении. Очень упрощенная версия того, что я делаю, это
(defroutes app-routes
(GET "/" [req] dump-req)
(route/not-found "Not Found"))
(defroutes api-routes
(GET "/api" [req] dump-req))
(def app
(routes (-> api-routes
(wrap-defaults api-defaults))
(-> app-routes
(wrap-defaults site-defaults))))
Обратите внимание, что существует больше промежуточного программного обеспечения, чем показано здесь.
Единственное "ограничение", с которым я столкнулся, заключается в том, что, поскольку в маршрутах приложения есть маршрут, не найденный, он должен быть последним, или он будет запущен, прежде чем найти маршруты api.
Это кажется более простым и гибким, чем некоторые другие решения, которые я нашел, которые, как представляется, используют дополнительное условное промежуточное программное обеспечение, такое как ring.middleware.conditional или то, что мне кажется более сложным определением маршрутизации, где есть дополнительный слой defroutes и необходимость определять defroutes с ЛЮБОЙ "*" и т.д.
Я подозреваю, что есть что-то тонкое, которого я здесь не вижу, и, хотя мой подход, похоже, работает, это приведет к неожиданному поведению или результатам в некоторых ситуациях и т.д.
Ответы
Ответ 1
Вы правы, вопросы заказа и недостаток, который вам не хватает - промежуточное программное обеспечение, которое вы применяете для api-routes
, выполняется для всех запросов.
Рассмотрим этот код:
(defn wrap-app-middleware
[handler]
(fn [req]
(println "App Middleware")
(handler req)))
(defn wrap-api-middleware
[handler]
(fn [req]
(println "API Middleware")
(handler req)))
(defroutes app-routes
(GET "/" _ "App")
(route/not-found "Not Found"))
(defroutes api-routes
(GET "/api" _ "API"))
(def app
(routes (-> api-routes
(wrap-api-middleware))
(-> app-routes
(wrap-app-middleware))))
и сеанс репликации:
> (require '[ring.mock.request :as mock])
> (app (mock/request :get "/api"))
API Middleware
...
> (app (mock/request :get "/"))
API Middleware
App Middleware
...
Compojure имеет приятную функцию и помощник, который применяет промежуточное ПО к маршрутам после того, как они были сопоставлены - wrap-routes
(def app
(routes (-> api-routes
(wrap-routes wrap-api-middleware))
(-> app-routes
(wrap-routes wrap-app-middleware))
(route/not-found "Not Found")))
> (app (mock/request :get "/api"))
API Middleware
...
> (app (mock/request :get "/"))
App Middleware
...
Ответ 2
Более простым решением может быть... (я использую это в своем приложении, я адаптировал код к вашему примеру)
(defn make-api-handler
[]
(-> api-routes
(wrap-defaults api-defaults)))
(defn make-app-handler
[]
(-> app-routes
(wrap-defaults site-defaults)))
(def app
(let [api-handler-fn (make-api-handler)
app-handler-fn (make-app-handler)]
(fn [request]
(if (clojure.string/starts-with? (:uri request) "/api")
(api-handler-fn request)
(app-handler-fn request)))))