使用Ring和Compojure为不同的中间件提供app和api路由

Tim*_*m X 14 clojure compojure ring

我有一个ring + compojure应用程序,我想应用不同的中间件,具体取决于路由是Web应用程序的一部分还是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))))
Run Code Online (Sandbox Code Playgroud)

请注意,中间件比我在此处显示的更多.

我遇到的唯一"限制"是,由于app-routes有未找到的路由,它需要到达最后或者在找到api路由之前触发它.

这似乎比我发现的其他一些解决方案更简单,更灵活,似乎要么使用额外的条件中间件,例如ring.middleware.conditional,或者在我看来是更复杂的路由定义,其中有一个额外的defroutes层并且需要用任何"*"等来定义defroutes.

我怀疑在这里有一些微妙的东西,虽然我的方法似乎有效,但它会导致意外行为或导致某些情况等.

Kyl*_*yle 18

你是对的,顺序很重要,而且你有一个微妙的缺失 - 你申请的中间件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))))
Run Code Online (Sandbox Code Playgroud)

和repl会话:

> (require '[ring.mock.request :as mock])
> (app (mock/request :get "/api"))
API Middleware
...
> (app (mock/request :get "/"))
API Middleware
App Middleware
...
Run Code Online (Sandbox Code Playgroud)

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
...
Run Code Online (Sandbox Code Playgroud)