使用Ring和Compojure为应用程序和API路由提供不同的中间件服务

15

我有一个使用ring+compojure编写的应用程序,我希望能够根据路由属于Web应用程序还是基于JSON的API来应用不同的中间件。

我在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))))

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

我遇到的唯一“限制”是,由于应用路由有未找到的路由,它需要放在最后,否则会在查找API路由之前触发。

这似乎比我发现的一些其他解决方案更简单和灵活,其他解决方案似乎要使用额外的条件中间件(例如ring.middleware.conditional)或者更复杂的路由定义,其中有一个额外的defroutes层并且需要使用ANY“*”等定义defroutes。

我怀疑我在这里漏掉了一些微妙之处,虽然我的方法似乎有效,但在某些情况下它将导致意想不到的行为或结果等。


这里的routes是什么?明白了:http://weavejester.github.io/compojure/compojure.core.html#var-routes - Ahmed Fasih
2个回答

20

你是正确的,顺序很重要,并且你错过了一个微妙之处-你应用于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 会话:

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

谢谢,你完全理解了我的问题,更重要的是,用 wrap-routes 指针指引我走向了正确的方向。 - Tim X
1
只需使用此方法标记警告。这种方法在使用site-defaults配置的ring wrap-defaults中无法正常工作。问题在于它会破坏路由/未找到处理程序。似乎是由于wrap-defaults将响应头插入空响应中,从而使其看起来像是处理程序已处理了请求。导致返回代码为200且没有内容。已在ring-defaults中记录了一个问题。 - Tim X
1
如果您有选择,可以创建两个环形应用程序并将它们安装在不同的Web上下文中。使用Immutant,您可以简单地创建两个处理程序,为两者使用不同的中间件,然后将它们安装在不同的路径上,例如(web/run app)(web/run api :path "/api") - egli

0
一个更简单的解决方案可能是...(我在我的应用程序中使用了这个,我将代码调整为您的示例)
(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)))))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接