Clojure Web 开发
HTTP 服务
Clojure 中,一般使用库将 HTTP 1请求抽象成一个普通的 map,其中包含 uri, method, headers, body(可以参照这类抽象的一个流行库 ring 对请求的抽象),然后书写针对这个 map 输入的函数用于处理该请求。
(fn [_req] {:status 200 :body "hello world"})
不过,一般我们会引入一个路由管理的库,来将请求 dispatch 到特定的请求处理函数,目前最为流行的路由管理的库应该就是 reitit 了2。
(require '[reitit.core :as r])(def router (r/router[["/api/ping" ::ping]["/api/orders/:id" ::order]]))(r/match-by-path router "/api/ping");; #Match{:template "/api/ping";; :data {:name ::ping} ; 很容易对路由给定一个请求的处理函数;; :result nil;; :path-params {};; :path "/api/ping"}
另外,有时希望执行一些通用的请求处理功能,比如校验请求验证信息(如请求头 Authentication 中的 token 信息),我们可以在自己的业务处理函数中调用相关逻辑,不过一般会通过 middleware3 或者 interceptor4 这种方式复用这部分逻辑。
;; a middleware(defn wrap-check-auth [h tokens](fn [{:keys [request] :as req}](let [authed? (some-> request (get-in [:headers "Authorization"]) tokens)](h (assoc req :authed? authed?)))))(defn hello [{:keys [authed?] :as req}]{:status 200 :body (str "hello " authed?)})(defn bye [{:keys [authed?] :as req}]{:status 200 :body (str "bye " authed?)})(def tokens #{"token-0"})(def hello' (wrap-check-auth hello tokens))(def bye' (wrap-check-auth bye tokens))
诸如 cookie 解析,form 数据解析,内容格式协商与转换(content negotiation)这类常见功能都有现成的 middleware/interceptor 的库5。
Clojure 也有一些类似 web 开发框架的库,比如 kit-clj, Luminus,可以使用它们生成项目,很容易从这些生成的项目入手开始定制你自己的项目。
状态管理
一种常见的实践是,我们将一个启动的应用看成是一些相互依赖的组件(其中一些是状态组件)构成的系统。
手工维护这些组件依赖关系与它们初始化是一件无聊的事情,有一些库帮助我们实现这个操作:Integrant, Component, Mount.
特别的,Integrant 更是允许你将应用系统的构建编程数据驱动的。
(require '[integrant.core :as ig]);; 实现组件 :adapter/jetty & :handler/greet(defmethod ig/init-key :adapter/jetty [_ {:keys [handler] :as opts}] ...)(defmethod ig/init-key :handler/greet [_ {:keys [name]}] ...);; 系统配置(数据)(def config {:adapter/jetty {:port 8080, :handler #ig/ref :handler/greet}:handler/greet {:name "Alice"}});; 配置 -> 运行时系统(def system (ig/init config))
Stuart Sierra 的 reloaded workflow 这篇文章也值得一读。