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

时间:2015-01-19 02:02:40

标签: clojure ring compojure

我有一个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))))

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

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

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

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

1 个答案:

答案 0 :(得分: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))))

和repl session:

> (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
...