讀古今文學網 > Java程序員修煉之道 > 13.6 Compojure入門 >

13.6 Compojure入門

開發Web最致命的想法就是把什麼網站都當成Google來設計。對於Web應用來說,過度設計和設計不足都是錯誤的。

務實而優秀的開發人員會考慮Web應用的上下文,不會增加任何不必要的複雜性。認真分析所有應用的非功能需求是避免構建錯誤的關鍵前提。

Compojure就是那種不妄想征服世界的Web框架。對於Web儀表板、操作監控,以及很多更加注重簡單性和開發速度、而不是大規模擴展能力及其他非功能需求的簡單任務來說,Compojure是非常理想的選擇。從這種描述中你應該能猜出來,Compojure介於多語言編程金字塔的領域特定層和動態層之間。

在這一節我們會搭建一個簡單的Hello World應用,然後討論Compojure把Web應用串起來的簡單規則。在用這些規則搭建示例程序之前,先介紹一個實用的Clojure HTML類庫(Hiccup)。

如圖13-6所示,Compojure構建在Ring框架之上,Ring框架是Clojure連接到Jetty Web容器的中間件。但使用Compojure/Ring並不需要對Jetty有多深入的瞭解。我們先用一個簡單的Hello World作為Compojure的入門應用吧。

圖13-6 Compojure和Ring

13.6.1 Hello Compojure

開始一個新的Compojure項目非常容易,因為Compojure跟Leiningen的工作流程自然融合。如果你還沒裝Leiningen,也沒看第12章中的那一節,那你現在就應該去把這兩件事做了,因為接下來的內容要求你熟悉Leiningen。

要開始一個新項目,只要執行一個普通的Leiningen命令:

lein new hello-compojure
  

在project.clj中可以輕鬆指明項目的依賴項。代碼清單13-8顯示了如何在project.clj文件中指定Hello World項目的依賴項。

代碼清單13-8 簡單的Compojure project.clj

(defproject hello-compojure \"1.0.0-SNAPSHOT\"
  :description \"FIXME: write description\"
  :dependencies [[org.clojure/clojure \"1.2.1\"]
                 [compojure \"0.6.2\"]]
  :dev-dependencies [[lein-ring \"0.4.0\"]]
  :ring {:handler hello-compojure.core/app})
  

(defproject)跟第12章那個很像,不過多了兩個元數據。

  • :dev-dependencies確保開發人員可以在開發時使用lein命令。稍後我們討論lein ring server時你就能見到實例了。

  • :ring引入了Ring類庫所需的掛鉤。它將Ring特定的元數據映射為參數。

這個例子中給Ring傳入了一個:handler屬性。看起來它希望得到hello-compojure.core命名空間中的app符號。我們來看看代碼清單13-9中core.clj中對它的聲明,以便找出它們是如何相互配合的。

代碼清單13-9 Compojure Hello World中簡單的core.clj文件

(ns hello-compojure.core
  (:use compojure.core)
  (:require [compojure.route :as route]
            [compojure.handler :as handler]))

(load \"hello\")

(defroutes main-routes ;
  (GET \"/\"  (page-hello-compojure)) //主路由定義

  (route/resources \"/\")
  (route/not-found \"Page not found\"))

(def app (handler/site main-routes)) //註冊路由 
  

這種把關聯信息和其他信息保存在core.clj中的慣例非常實用。當有URL請求時再加載一個包含對應函數(頁面函數)的單獨文件很簡單。這確實只是一個為了提高可讀性,簡單實現關注點分離的慣例。

Compojure使用了一組規則,稱為路由,來確定如何處理接入的HTTP請求。這些規則是由Compojure依賴的Ring框架提供的,它們既簡單又實用。你可能已經猜出來了,規則GET\"/\"告訴Web服務器如何處理對根URL的GET請求。我們下一節會對路由做更多的討論。

為了完成這個例子的代碼,還需要在src/hello_compojure目錄中創建hello.clj文件。在這個文件中要定義一個如下所示的頁面函數(page-hello-compojure)

(ns hello-compojure.core)
(defn page-hello-compojure  \"<h1>Hello Compojure</h1>\")
  

這個頁面函數是個常規的Clojure函數,它會返回一個字符串作為HTML文檔的<body>標籤中的內容,而這個文檔會作為響應的一部分返回給用戶。

讓我們把這個例子跑起來。在Compojure中這是個十分簡單的操作。先確保所有依賴項都裝上了:

ariel:hello-compojure boxcat$ lein deps
Downloading: org/clojure/clojure/1.2.1/clojure-1.2.1.pom from central
Downloading: org/clojure/clojure/1.2.1/clojure-1.2.1.jar from central
Copying 9 files to /Users/boxcat/projects/hello-compojure/lib
Copying 17 files to /Users/boxcat/projects/hello-compojure/lib/dev
 

到目前為止一切都好。現在需要把它跑起來,可以用Ring提供的ring server方法。

ariel:hello-compojure boxcat$ lein ring server
2011-04-11 18:02:48.596:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
2011-04-11 18:02:48.615:INFO::jetty-6.1.26
2011-04-11 18:02:48.743:INFO::Started [email protected]:3000
Started server on port 3000
  

這會啟動一個簡單的Ring/Jetty Web服務器(默認端口3000),以實現快速反饋。默認情況下,這個服務器會自動重載被修改的文件。

警告 需要知道開發服務器的重載是在文件這一層實現的。這意味著正在運行的服務器可能會因為重新加載頁面導致其狀態被沖掉(或更糟,被部分沖掉)。如果你懷疑發生了這種情況,並因此出現了問題,應該關掉服務器重新啟動。啟動Ring/Jetty很快,應該不會對開發時間有太大影響。

如果用瀏覽器訪問開發機上的3000端口(或本機http://127.0.0.1:3000),應該會看到頁面中顯示出了「Hello Compojure」。

13.6.2 Ring和路由

我們來看看如何配置Compojure應用的路由。路由的定義應該能讓你想到一種領域特定語言:

(GET \"/\"  (page-hello-compojure))
  

這些路由規則應當被看做匹配接入請求的規則。其構成方式非常簡單:

(<HTTP 方法> <URL> <參數> <動作>)
  
  • HTTP方法,通常是GETPOST,但Compojure也支持PUTDELETEHEAD。如果要匹配這條規則,這個HTTP方法必須跟傳入的請求相匹配。

  • URL,請求對應的URL。如果要匹配這條規則,這個URL必須跟傳入的請求相匹配。

  • 參數,一個表示參數應該如何處理的表達式。很快我們就會對它展開討論。

  • 動作,與這條規則匹配時返回的表達式(通常表示為傳入參數的函數調用)。

對這些規則的匹配按從上到下的順序逐一比對,直到找到匹配項。Compojure會執行第一個匹配項的動作,表達式的值會作為返回文檔<body>標籤中的內容。

Compojure中規則的定義很靈活。比如說,創建一個從URL中提取函數參數的規則非常簡單。我們來改一下代碼清單13-5中的Hello World路由:

(defroutes main-routes
  (GET \"/\"  (page-hello-compojure))
  (GET [\"/hello/:fname\", :fname #\"[a-zA-Z]+\" ] [fname] (page-hello-with-name fname))

  (route/resources \"/\")
  (route/not-found \"Page not found\"))
  

這個新規則只匹配包含/hello/<名稱>的URL。其中的名稱只能包含字母(大寫、小寫或大小寫組合都行),這是由Clojure的正則表達式#\"[a-zA-Z]+\"限定的。

如果匹配了這一規則,Compojure會以匹配的名稱為參數調用(page-hello-with-name)。函數定義非常簡單:

(defn page-hello-with-name [fname]
  (str \"<h1>Hello from Compojure \" fname \"</h1>\"))
  

只有非常簡單的應用才能用這種內聯HTML,否則很快就會變成一種痛。好在有Hiccup模塊,它為需要輸出HTML的Web應用提供了很多實用的功能。馬上我們就去看看。

13.6.3 Hiccup

要在hello-compojure應用中掛上Hiccup,需要做三件事:

  • 在project.clj上加上依賴項,如[hiccup \"0.3.4\"]
  • 再次運行lein deps
  • 重啟Web容器。

很好。現在我們來看看在Clojure內部怎麼用Hiccup寫出更好的HTML形式。

Hiccup提供的關鍵形式之一是(html)。用它可以非常直接地編寫HTML。下面是用Hiccup重寫的(page-hello-with-name)

(defn page-hello-html-name [fname]
   (html [:h1 \"Hello from Compojure \" fname]
         [:p [:p \"Paragraph text\"]]))
  

現在這些嵌套格式的HTML標籤看起來很像Clojure代碼,所以把它放到代碼裡自然多了。(html)形式以一個或更多的(標籤)向量為參數,並且標籤的嵌套深度不受限制。

接下來,我們會向你介紹一個稍微大一點兒的應用,一個給水獺投票的網站。