使用 Ring 和 Compojure 使用不同的中间件服务应用程序和 api 路由

Serving app and api routes with different middleware using Ring and Compojure

我有一个 ring+compojure 应用程序,我想根据路由是 Web 应用程序的一部分还是 api(基于 json)的一部分来应用不同的中间件。

我在 stack overflow 和其他论坛上找到了这个问题的一些答案,但这些答案似乎比我一直使用的解决方案更复杂。我想知道我的做法是否有缺点,以及我的解决方案中可能缺少什么。我正在做的一个非常简化的版本是

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

请注意,中间件比我在这里展示的要多。

我遇到的唯一'restriction'是因为app-routes有未找到的路由,所以它需要排在最后,否则会在找到api路由之前触发。

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

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

你是对的,顺序很重要,你遗漏了一个微妙之处 - 你应用于 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
...

一个更简单的解决方案可能是...(我在我的应用程序中使用它,我根据您的示例调整了代码)

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