组合路线背后的“大创意”是什么?

时间:2010-08-15 17:05:58

标签: clojure compojure

我是Clojure的新手,并且一直在使用Compojure编写基本的Web应用程序。不过,我正在用Compojure的defroutes语法碰壁,我认为我需要理解它背后的“如何”和“为什么”。

似乎Ring样式的应用程序以HTTP请求映射开始,然后通过一系列中间件函数传递请求,直到它被转换为响应映射,然后将其发送回浏览器。对于开发人员来说,这种风格似乎太“低级”,因此需要像Compojure这样的工具。我可以看到在其他软件生态系统中需要更多的抽象,尤其是Python的WSGI。

问题在于我不了解Compojure的方法。我们采用以下defroutes S表达式:

(defroutes main-routes
  (GET "/"  [] (workbench))
  (POST "/save" {form-params :form-params} (str form-params))
  (GET "/test" [& more] (str "<pre>" more "</pre>"))
  (GET ["/:filename" :filename #".*"] [filename]
    (response/file-response filename {:root "./static"}))
  (ANY "*"  [] "<h1>Page not found.</h1>"))

我知道理解所有这些的关键在于一些宏观巫术,但我还不完全理解宏(还)。我很长一段时间都盯着defroutes来源了,但是没有得到它!这里发生了什么?理解“大创意”可能会帮助我回答这些具体问题:

  1. 如何从路由功能(例如workbench功能)中访问Ring环境?例如,假设我想访问HTTP_ACCEPT标头或请求/中间件的其他部分?
  2. 解构的问题是什么({form-params :form-params})?在解构时我可以使用哪些关键字?
  3. 我真的很喜欢Clojure,但我很难过!

5 个答案:

答案 0 :(得分:208)

答案 1 :(得分:7)

詹姆斯·里夫斯(作为Compojure的作者)在booleanknot.com发表了一篇精彩的文章,并且阅读了它,并且点击了#34;对我来说,所以我在这里重新翻译了一些(真的是我所做的一切)。

还有一个幻灯片here from the same author,可以回答这个问题。

Compojure基于Ring,它是http请求的抽象。

A concise syntax for generating Ring handlers.

那么,Ring handlers是什么?摘自doc:

;; Handlers are functions that define your web application.
;; They take one argument, a map representing a HTTP request,
;; and return a map representing the HTTP response.

;; Let's take a look at an example:

(defn what-is-my-ip [request]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body (:remote-addr request)})

非常简单,但也很低级。 可以使用ring/util库更简洁地定义上述处理程序。

(use 'ring.util.response)

(defn handler [request]
  (response "Hello World"))

现在我们想根据请求调用不同的处理程序。 我们可以这样做一些静态路由:

(defn handler [request]
  (or
    (if (= (:uri request) "/a") (response "Alpha"))
    (if (= (:uri request) "/b") (response "Beta"))))

并像这样重构:

(defn a-route [request]
  (if (= (:uri request) "/a") (response "Alpha")))

(defn b-route [request]
  (if (= (:uri request) "/b") (response "Beta"))))

(defn handler [request]
  (or (a-route request)
      (b-route request)))

詹姆斯当时注意到的有趣的事情是,它允许嵌套路线,因为&#34;将两条或更多条路线组合在一起的结果本身就是一条路线&#34;。

(defn ab-routes [request]
  (or (a-route request)
      (b-route request)))

(defn cd-routes [request]
  (or (c-route request)
      (d-route request)))

(defn handler [request]
  (or (ab-routes request)
      (cd-routes request)))

到目前为止,我们开始看到一些看起来像使用宏可以考虑的代码。 Compojure提供了defroutes宏:

(defroutes ab-routes a-route b-route)

;; is identical to

(def ab-routes (routes a-route b-route))

Compojure提供其他宏,例如GET宏:

(GET "/a" [] "Alpha")

;; will expand to

(fn [request#]
  (if (and (= (:request-method request#) ~http-method)
           (= (:uri request#) ~uri))
    (let [~bindings request#]
      ~@body)))

生成的最后一个函数看起来像我们的处理程序!

请务必查看James post,因为它会进行更详细的说明。

答案 2 :(得分:3)

答案 3 :(得分:3)

对于那些仍在努力寻找路线发生情况的人来说,可能就像我一样,你不理解解构的想法。

实际上阅读the docs for let有助于清除整个“魔法价值来自哪里?”问题

我正在粘贴以下相关部分:

  

Clojure支持抽象结构   绑定,通常称为解构,   在let绑定列表中,fn参数   列表以及扩展到的任何宏   一个让或fn。基本的想法是a   binding-form可以是数据结构   含有符号的文字   绑定到的各个部分   INIT-EXPR。绑定是抽象的   矢量文字可以绑定到   任何顺序的,而a   map literal可以绑定到任何内容   是联想的。

     

Vector binding-exprs允许你绑定   命名为连续事物的一部分   (不仅仅是矢量),像矢量,   列表,seqs,字符串,数组和   任何支持nth的东西。基础的   顺序形式是一个矢量   绑定形式,将被绑定   来自的连续元素   init-expr,通过nth查找。在   另外,和任选地,&amp;其次   通过绑定形式将导致   绑定形式绑定到   序列的其余部分,即   部分尚未约束,抬头看看   nthnext。最后,也可选,:as   后跟一个符号会导致这种情况   符号被绑定到整个   INIT-EXPR:

(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
  [a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]
  

Vector binding-exprs允许你绑定   命名为连续事物的一部分   (不仅仅是矢量),像矢量,   列表,seqs,字符串,数组和   任何支持nth的东西。基础的   顺序形式是一个矢量   绑定形式,将被绑定   来自的连续元素   init-expr,通过nth查找。在   另外,和任选地,&amp;其次   通过绑定形式将导致   绑定形式绑定到   序列的其余部分,即   部分尚未约束,抬头看看   nthnext。最后,也可选,:as   后跟一个符号会导致这种情况   符号被绑定到整个   INIT-EXPR:

(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
  [a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]

答案 4 :(得分:1)

  

解构的问题是什么({form-params:form-params})?在解构时我可以使用哪些关键字?

可用的键是输入映射中的键。可以在let和doseq形式内部进行解构,或者在fn或defn的参数内部进行解构

以下代码有望提供信息:

(let [{a :thing-a
       c :thing-c :as things} {:thing-a 0
                               :thing-b 1
                               :thing-c 2}]
  [a c (keys things)])

=> [0 2 (:thing-b :thing-a :thing-c)]

一个更高级的示例,显示了嵌套解构:

user> (let [{thing-id :id
             {thing-color :color :as props} :properties} {:id 1
                                                          :properties {:shape
                                                                       "square"
                                                                       :color
                                                                       0xffffff}}]
            [thing-id thing-color (keys props)])
=> [1 16777215 (:color :shape)]

明智地使用时,解构通过避免样板数据访问来淡化代码。通过使用:as和打印结果(或结果的键),您可以更好地了解您可以访问的其他数据。