如何简化 Compojure 路线?

How do I simplify Compojure routes?

我有以下代码在 Compojure 中定义我的路线:

(ns my-project.my-test
  (:gen-class)
  (:require
   [my-test.template-views :refer :all]
   [compojure.core :refer [defroutes GET POST context]]
   [compojure.route :as route]
   [org.httpkit.server :refer [run-server]]))

(defn wrap-request
  [handler]
  (fn [request]
    (let [{remote-addr :remote-addr uri :uri scheme :scheme request-method :request-method} request]
      (println (str "REQUEST: " request)))
    (handler request)))

(defroutes app
  (wrap-request
   (GET "/" request
     {:status 200
      :headers {"Content-Type" "text/html"}
      :body (template-body (:uri request))}))
  (wrap-request
    (GET "/page1" request
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body (template-body (:uri request))}))
  (wrap-request
    (GET "/page2" request
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body (template-body (:uri request))}))
  (wrap-request
    (GET "/page3" request
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body (template-body (:uri request))}))
  (route/resources "/")
  (route/not-found {:status 404
                    :headers {"Content-Type" "text/html"}
                    :body "<h1>Not Found</h1>"}))

可行,但似乎我应该能够像这样简化它:

(ns my-project.my-test
  (:gen-class)
  (:require
   [my-test.template-views :refer :all]
   [compojure.core :refer [defroutes GET POST context]]
   [compojure.route :as route]
   [org.httpkit.server :refer [run-server]]))

(defn wrap-request
  [handler]
  (fn [request]
    (let [{remote-addr :remote-addr uri :uri scheme :scheme request-method :request-method} request]
      (println (str "REQUEST: " request)))
    (handler request)))

(defn wrap-template
  [route]
  (wrap-request
   (GET route request
     {:status 200
      :headers {"Content-Type" "text/html"}
      :body (template-body (:uri request))})))

(defroutes app
  (map wrap-template ["/" "/page1" "/page2" "/page3"])
  (route/resources "/")
  (route/not-found {:status 404
                    :headers {"Content-Type" "text/html"}
                    :body "<h1>Not Found</h1>"}))

但是,当我这样做时,我得到了这个错误回溯:

Sat Apr 24 22:38:33 MDT 2021 [worker-2] ERROR - GET /page2
java.lang.ClassCastException: class clojure.lang.LazySeq cannot be cast to class clojure.lang.IFn (clojure.lang.LazySeq and clojure.lang.IFn are in unnamed module of loader 'app')
    at compojure.core$routing$fn__368163.invoke(core.clj:185)
    at clojure.core$some.invokeStatic(core.clj:2705)
    at clojure.core$some.invoke(core.clj:2696)
    at compojure.core$routing.invokeStatic(core.clj:185)
    at compojure.core$routing.doInvoke(core.clj:182)
    at clojure.lang.RestFn.applyTo(RestFn.java:139)
    at clojure.core$apply.invokeStatic(core.clj:669)
    at clojure.core$apply.invoke(core.clj:662)
    at compojure.core$routes$fn__368167.invoke(core.clj:192)
    at org.httpkit.server.HttpHandler.run(RingHandler.java:117)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

这里使用 (map) 有什么不对的地方吗?

routes(因此 defroutesexpects each argument to be a request handler function。处理程序列表不是处理程序函数;因此错误。令人高兴的是,有一个函数可以将处理程序列表转换为单个处理程序:routes!由于它需要 N 个单独的参数,而不是单个列表,因此您还需要 apply。所以:

(defroutes app
  (apply routes (map wrap-template ["/" "/page1" "/page2" "/page3"]))
  (route/resources "/")
  (route/not-found {:status 404
                    :headers {"Content-Type" "text/html"}
                    :body "<h1>Not Found</h1>"}))

顺便说一句,我通常建议不要使用 defroutes,因为它不像单独的 def + routes 那样容易组合,而且对于初学者来说,它会导致忘记除了 defroutes 之外的任何东西都存在,而实际上大多数有趣的服务器都希望将一个函数应用于它们的某些路由。