使用clojure构建web应用程序,一般会用到这两个函数库。其中,ring负责请求和响应数据结构的转换,compojure用于路由。
当然上层还会有enlive,hiccup等web模版化的函数库。
Ring
请求和响应数据结构的转换在ring.util.servlet命名空间中实现
(defn servlet
"Create a servlet from a Ring handler.."
[handler]
(proxy [HttpServlet] []
(service [request response]
((make-service-method handler)
this request response))))
该函数返回一个实现了HttpServlet接口的实例,并在service中对请求和响应数据进行了转换,其中handler是一个函数,接收一个包含请求信息的map,返回一个响应的map
make-service-method函数中通过调用build-request-map将HttpServletRequest实例转换成一个最基本的map
(defn build-request-map
"Create the request map from the HttpServletRequest object."
[^HttpServletRequest request]
{:server-port (.getServerPort request)
:server-name (.getServerName request)
:remote-addr (.getRemoteAddr request)
:uri (.getRequestURI request)
:query-string (.getQueryString request)
:scheme (keyword (.getScheme request))
:request-method (keyword (.toLowerCase (.getMethod request)))
:headers (get-headers request)
:content-type (.getContentType request)
:content-length (get-content-length request)
:character-encoding (.getCharacterEncoding request)
:ssl-client-cert (get-client-cert request)
:body (.getInputStream request)})
在handler处理完后,update-servlet-response函数又将一个响应的map信息设置到HttpServletResponse实例中,ring中还定义了一些基本的包装handler的函数,对request map和response map添加其他的key, value。如wrap-cookie, wrap-file等。
Compojure
通过compojure可以定义一个路由,指定不同消息的处理方法,示例如下
(defroutes app
(GET "/" [] "<h1>Hello World</h1>")
(route/not-found "<h1>Page not found</h1>"))
defroutes函数其实定义了一个函数,而在宏中的每一个form,如GET,route/not-found都是一个handler。defroutes宏最终将转换成这样一个函数
(defn xxx
[request]
(some #(% request) handlers))
其实,GET也是一个宏,类似的宏还有POST,HEAD,DELETE等,这些宏生成的handler会先根据请求方法,以及路径匹配进行判断,如果不匹配则返回nil,这样就可以较给下一个handler处理, 这类宏大概转换后的函数可参考make-route
(defn make-route
"Returns a function that will only call the handler if the method and Clout
route match the request."
[method route handler]
(if-method method
(if-route route
(fn [request]
(render (handler request) request)))))