0. 流程圖
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>