01 Clojure Web 程序基本架構

0. 流程圖

01 - Clojure Web 程序基本結構.png

1. 創(chuàng)建項目

lein new soul-talk

2. 配置 Git

初始化 Git 倉庫

git init

設置 .gitignore 忽略項

/target
/classes
/checkouts
/target
/classes
/checkouts
profiles.clj
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
/.prepl-port
.hgignore
.hg/

figwheel_server.log
/resources/public/js
db.sqlite

提交到 GitHub

到 Github 創(chuàng)建倉庫陆淀,然后

git remote add origin https://github.com/myqiao/soul-talk.git
git push -u origin master

3. 配置依賴

配置國內源

lein 的官方源經常訪問不到考余,在項目配置文件中添加鏡像

 ;; 配置鏡像庫
  :mirrors {"central"  {:name "aliyun"
                        :url  "https://maven.aliyun.com/repository/central"}
            #"clojars" {:name         "tsinghua"
                        :url          "https://mirrors.tuna.tsinghua.edu.cn/clojars/"
                        :repo-manager true}}

添加各種依賴庫

:dependencies [
        ;; Clojure 主運行時庫
        [org.clojure/clojure "1.9.0"]

        ;; Ring 庫
        [ring "1.7.1"]

        ;; 基于 Ring 的 Response 簡化工具庫
        [metosin/ring-http-response "0.9.1"]

        ;; 常用中間件集合
        [ring/ring-defaults "0.3.2"]

        ;; 路由庫
        [compojure "1.6.1"]]

配置并運行 lein-ancient 依賴版本管理插件

在配置文件 project.clj 中添加

  :profiles {
        :user {
            :dependencies []
            :plugins [[lein-ancient "0.6.15"]]}}

運行命令 lein ancient upgrade :interactive ,將項目的依賴升級到最新版本

4. Handler 原理講解

Handler 就是一個普通函數轧苫,接受一個 request 楚堤,返回一個 response ,二者都是 Clojure 哈希表結構

5. 中間件

中間件原理講解

中間件就是一個普通函數含懊,他接受一個 handler 函數身冬,返回一個 handler 函數

這有點像俄羅斯套娃:

  • 中間件返回的 handler 是外層的套娃
  • 外層套娃持有一個內層套娃,就是中間件接收的那個 handler

每個中間件都會增加一層套娃岔乔,可以一層一層的套下去

請求響應數據流

  • 最外層的 handler 總是最先接受到服務器請求
  • 請求從最外層一層一層向內傳遞酥筝,直到最內層
  • 每一層在向內傳遞的請求過程中可以對請求進行處理
  • 到最內層后,根據請求生成響應
  • 響應從最內層一層一層向外傳遞雏门,直到最外層
  • 每一層在向外傳遞響應的過程中可以對響應進行處理
  • 最外層的 handler 處理完響應后嘿歌,返回給服務器

創(chuàng)建自定義中間件 wrap-nocache

創(chuàng)建一個自定義的中間件 wrap-nocache,只處理響應茁影,不處理請求宙帝。功能是在響應頭信息中添加不緩存設置

;; 自定義中間件:加入不緩存頭信息
(defn wrap-nocache [handler]
  (fn [request]
    (-> request
        handler
        (assoc-in [:headers "Pragma"] "no-cache"))))

引入 Ring 內置熱重載中間件 wrap-reload

這是一個 Ring 內置的中間件 wrap-reload,但是目前自動載入還不好使募闲,必須等 Ring 插件配置好才好使

(ns soul-talk.core
  (:require ......
            [ring.middleware.reload :refer [wrap-reload]])) ;; 添加

引入默認常用中間件 ring-defaults

依賴:[ring/ring-defaults "0.3.2"]

ring-defaults 庫包含了四種中間件:

  • api-defaults
  • site-defaults
  • secure-api-defaults
  • secure-site-defaults

相當于啟用了會話步脓、快閃、調試浩螺、頭信息沪编、文件上傳等等一系列內置中間件

(ns soul-talk.core
  (:require ......
    ;; 引入常所用中間件
    [ring.middleware.defaults :refer :all]))

6. 添加靜態(tài)資源

創(chuàng)建靜態(tài) index.html 模板頁面

靜態(tài)資源一般放置在 resources 下,這個路徑可以直接被 io/resource 函數和其他 io 函數讀取

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
這是一個主頁;
</body>
</html>

7. 創(chuàng)建自定義 Handler

再次強調年扩,Handler 處理請求,返回響應

創(chuàng)建 home-handle

home-handle 中访圃,直接讀取 index.html 返回

(ns soul-talk.core
  (:require ......
    [clojure.java.io :as io]))

;; 自定義 Handler厨幻,讀取靜態(tài)頁面返回
(defn home-handle [request]
  (io/resource "index.html"))

注意:在 resources 目錄 下的資源可以被程序讀取,而且不需要加路徑腿时,但是不能被用戶直接訪問

響應的生成方式(原理講解)

響應就是一個字符串或者一個哈希表 况脆,其生成方式有以下幾種

  • 直接返回一個字符串
  • 直接返回一個哈希表
  • 直接讀取靜態(tài)頁面返回
  • 利用 Response 庫構造響應哈希表

演示:使用簡化 Response 庫構造響應

由于 Ring 自帶的 ring.util.response 的功能比較基礎,需要自己寫返回碼批糟、頭信息等等格了,我們可以使用 ring-http-response 庫來簡化代碼。

下面的代碼并不是項目中的代碼徽鼎,只是用來演示

(ns soul-talk.core
  (:require ......
    [ring.util.http-response :as resp]))

(defn home-handle [request]
  ;; 這里簡化了代碼
  (resp/ok (str "<html><body><body>your IP is"
                (:remote-addr request) 
                "</body></html>")))

8. 路由

路由原理講解

對路徑字符串進行模式匹配盛末,返回對應的 handler弹惦,并將這個 handler 綁定到路由變量上。因此路由變量就是一個 handler

使用 Compojure 路由庫

依賴:[compojure "1.6.1"]

;; 引入路由函數
(ns soul-talk.core
  (:require ......
        [compojure.core :refer [routes GET]]
        [compojure.route :as route]))

定義路由規(guī)則

定義路由規(guī)則悄但。注意:最終的路由變量 app-route 其實也是一個 Handler

;; 創(chuàng)建路由規(guī)則棠隐,最終返回的是一個普通的 Handler
(def app-routes
  (routes
    (GET "/" request (home-handle request))
    (GET "/about" [] (str "這是關于我的頁面"))
    (route/not-found "<h1>Page not found</h1>")))

9. 程序啟動入口

需要告訴程序,哪個函數是程序啟動函數檐嚣≈螅可以在配置文件中直接指定啟動入口函數

;; 直接指定啟動入口函數
:main soul-talk.core/foo

也可以只指定命名空間,則命名空間中的 -main 函數自動成為啟動入口函數

;; 指定入口模塊嚎京,`-main` 函數自動成為啟動入口函數
:main soul-talk.core

10. 請求入口

原理講解:請求入口

==前面講過嗡贺,服務器接收到的請求,會首先送給就是套娃最外層的 handler 鞍帝,即最后一個中間件返回的 handler ==

路由變量是一個 handler诫睬,一般要作為最內層的 handler ,因此它會作為第一個中間件的參數

而最后一個中間件返回的handler 膜眠,就是最外層的 handler岩臣,即請求入口 handler

創(chuàng)建請求入口 Handler

將服務器請求、中間件宵膨、Handler 架谎、路由組合成一個 Handler ,相當于一個成品套娃辟躏,命名為 app

==注意:路由總是作為鏈式調用的第一個參數谷扣,即作為最內層的原生 Handler==

(def app
  (-> app-routes  ;; 鏈式調用的第一個參數為路由 Handler
      wrap-nocache
      ;; 自動重載中間件
      wrap-reload
      ;; 常用中間件
      (wrap-defaults site-defaults)))

11. 啟動服務

原理講解:服務器啟動方式

服務器啟動,只需要把請求入口 handler 傳給 jetty-run 函數捎琐,并配置一定的參數即可会涎。有兩種方式

從主函數啟動

在主函數中調用 jetty/run-jetty ,將服務啟動入口 Handler 傳給他即可

(ns soul-talk.core
  (:require ......
        [ring.adapter.jetty :as jetty]))
        
(defn -main []
  (jetty/run-jetty app {:port 3000 :join? false}))

從 Ring 插件啟動

使用 lein-ring 插件瑞凑,需要給插件指定服務啟動入口 Handler末秃。在 project.clj 中添加以下代碼:

:plugins [[lein-ring "0.12.4"]]

;; 插件不通過 main 函數啟動,只需要指定一個入口 Handler
:ring {:handler soul-talk.core/app}

12. 測試運行

從主函數 -main 啟動

lein run

從 Ring 插件啟動

使用插件啟動后籽御,自動載入中間件才好使

lein ring server ;; 默認端口 3000
lein ring server 4000 ;; 
lein ring server-headless ;; 不會打開瀏覽器

13. 最終代碼

project.clj

(defproject soul-talk "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.9.0"]
                 
                 ;; Ring 庫
                 [ring "1.7.1"]
                 
                 ;; 基于 Ring 的 Response工具庫
                 [metosin/ring-http-response "0.9.1"]
                 
                 ;; 常用中間件集合
                 [ring/ring-defaults "0.3.2"]
                 
                 ;; 路由庫
                 [compojure "1.6.1"]]
  
  
  ;; 基于 Lein 的 Ring 插件
  :plugins [[lein-ring "0.12.4"]]

  ;; 插件不通過 main 函數啟動练慕,只需要指定一個入口 Handler
  :ring {:handler soul-talk.core/app}
  
  ;; 不使用插件的時候,程序仍然從 main 函數啟動
  :main soul-talk.core
  
  
  :profiles {
        :user {
            :dependencies []
            :plugins [[lein-ancient "0.6.15"]]}})

src/soul-takl/core.clj

(ns soul-talk.core
  (:require
    ;; 標準庫 io 函數
    [clojure.java.io :as io]

    ;; 響應簡化庫
    [ring.util.http-response :as resp]

    ;; 中間件
    [ring.middleware.reload :refer [wrap-reload]]
    [ring.middleware.defaults :refer :all]

    ;; 路由庫
    [compojure.core :refer [routes GET]]
    [compojure.route :as route]

    ;; 服務啟動函數
    [ring.adapter.jetty :as jetty])) 


;; ************************************************
;; Handler 區(qū)域
;; ************************************************

;; 自定義 Handler技掏,讀取靜態(tài)頁面返回
(defn home-handle [request]
  (io/resource "index.html"))



;; ************************************************
;; 路由 區(qū)域
;; ************************************************

;; 創(chuàng)建路由規(guī)則铃将,最終返回的是一個普通的 Handler
(def app-routes
  (routes
    (GET "/" request (home-handle request))
    (GET "/about" [] (str "這是關于我的頁面"))
    (route/not-found "<h1>Page not found</h1>")))


;; ************************************************
;; 中間件 區(qū)域
;; ************************************************

;; 自定義中間件:加入不緩存頭信息
(defn wrap-nocache [handler]
  (fn [request]
    (-> request
        handler
        (assoc-in [:headers "Pragma"] "no-cache"))))



;; ************************************************
;; 啟動代碼 區(qū)域
;; ************************************************

(def app
  (-> app-routes  ;; 鏈式調用的第一個參數為路由 Handler
      wrap-nocache
      ;; 自動重載中間件
      wrap-reload
      ;; 常用中間件
      (wrap-defaults site-defaults)))


(defn -main []
  (jetty/run-jetty app {:port 3000 :join? false}))

resources/index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
這是一個主頁;
</body>
</html>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哑梳,隨后出現(xiàn)的幾起案子劲阎,更是在濱河造成了極大的恐慌,老刑警劉巖鸠真,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悯仙,死亡現(xiàn)場離奇詭異龄毡,居然都是意外死亡,警方通過查閱死者的電腦和手機雁比,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門稚虎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人偎捎,你說我怎么就攤上這事蠢终。” “怎么了茴她?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵寻拂,是天一觀的道長。 經常有香客問我丈牢,道長祭钉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任己沛,我火速辦了婚禮慌核,結果婚禮上,老公的妹妹穿的比我還像新娘申尼。我一直安慰自己垮卓,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布师幕。 她就那樣靜靜地躺著粟按,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霹粥。 梳的紋絲不亂的頭發(fā)上灭将,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音后控,去河邊找鬼庙曙。 笑死,一個胖子當著我的面吹牛浩淘,可吹牛的內容都是我干的捌朴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼馋袜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舶斧?” 一聲冷哼從身側響起欣鳖,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茴厉,沒想到半個月后泽台,有當地人在樹林里發(fā)現(xiàn)了一具尸體什荣,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年怀酷,在試婚紗的時候發(fā)現(xiàn)自己被綠了稻爬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜕依,死狀恐怖桅锄,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情样眠,我是刑警寧澤友瘤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站檐束,受9級特大地震影響辫秧,放射性物質發(fā)生泄漏。R本人自食惡果不足惜被丧,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一盟戏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧甥桂,春花似錦柿究、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至糕簿,卻和暖如春探入,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背懂诗。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工蜂嗽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人殃恒。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓植旧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親离唐。 傳聞我的和親對象是個殘疾皇子病附,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容