0. 流程圖

1. 創(chuàng)建項目
lein new soul-talk
2. 配置 Git
初始化 Git 倉庫
git init
設(shè)置 .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. 配置依賴
配置國內(nèi)源
lein 的官方源經(jīng)常訪問不到,在項目配置文件中添加鏡像
;; 配置鏡像庫
: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 就是一個普通函數(shù),接受一個 request ,返回一個 response ,二者都是 Clojure 哈希表結(jié)構(gòu)
5. 中間件
中間件原理講解
中間件就是一個普通函數(shù),他接受一個 handler 函數(shù),返回一個 handler 函數(shù)
這有點像俄羅斯套娃:
- 中間件返回的
handler是外層的套娃 - 外層套娃持有一個內(nèi)層套娃,就是中間件接收的那個
handler
每個中間件都會增加一層套娃,可以一層一層的套下去
請求響應(yīng)數(shù)據(jù)流
- 最外層的
handler總是最先接受到服務(wù)器請求 - 請求從最外層一層一層向內(nèi)傳遞,直到最內(nèi)層
- 每一層在向內(nèi)傳遞的請求過程中可以對請求進行處理
- 到最內(nèi)層后,根據(jù)請求生成響應(yīng)
- 響應(yīng)從最內(nèi)層一層一層向外傳遞,直到最外層
- 每一層在向外傳遞響應(yīng)的過程中可以對響應(yīng)進行處理
- 最外層的
handler處理完響應(yīng)后,返回給服務(wù)器
創(chuàng)建自定義中間件 wrap-nocache
創(chuàng)建一個自定義的中間件 wrap-nocache,只處理響應(yīng),不處理請求。功能是在響應(yīng)頭信息中添加不緩存設(shè)置
;; 自定義中間件:加入不緩存頭信息
(defn wrap-nocache [handler]
(fn [request]
(-> request
handler
(assoc-in [:headers "Pragma"] "no-cache"))))
引入 Ring 內(nèi)置熱重載中間件 wrap-reload
這是一個 Ring 內(nèi)置的中間件 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-defaultssite-defaultssecure-api-defaultssecure-site-defaults
相當于啟用了會話、快閃、調(diào)試、頭信息、文件上傳等等一系列內(nèi)置中間件
(ns soul-talk.core
(:require ......
;; 引入常所用中間件
[ring.middleware.defaults :refer :all]))
6. 添加靜態(tài)資源
創(chuàng)建靜態(tài) index.html 模板頁面
靜態(tài)資源一般放置在 resources 下,這個路徑可以直接被 io/resource 函數(shù)和其他 io 函數(shù)讀取
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
這是一個主頁;
</body>
</html>
7. 創(chuàng)建自定義 Handler
再次強調(diào),Handler 處理請求,返回響應(yīng)
創(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目錄 下的資源可以被程序讀取,而且不需要加路徑,但是不能被用戶直接訪問
響應(yīng)的生成方式(原理講解)
響應(yīng)就是一個字符串或者一個哈希表 ,其生成方式有以下幾種
- 直接返回一個字符串
- 直接返回一個哈希表
- 直接讀取靜態(tài)頁面返回
- 利用 Response 庫構(gòu)造響應(yīng)哈希表
演示:使用簡化 Response 庫構(gòu)造響應(yīng)
由于 Ring 自帶的 ring.util.response 的功能比較基礎(chǔ),需要自己寫返回碼、頭信息等等,我們可以使用 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. 路由
路由原理講解
對路徑字符串進行模式匹配,返回對應(yīng)的 handler,并將這個 handler 綁定到路由變量上。因此路由變量就是一個 handler
使用 Compojure 路由庫
依賴:[compojure "1.6.1"]
;; 引入路由函數(shù)
(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 "這是關(guān)于我的頁面"))
(route/not-found "<h1>Page not found</h1>")))
9. 程序啟動入口
需要告訴程序,哪個函數(shù)是程序啟動函數(shù)??梢栽谂渲梦募兄苯又付▎尤肟诤瘮?shù)
;; 直接指定啟動入口函數(shù)
:main soul-talk.core/foo
也可以只指定命名空間,則命名空間中的 -main 函數(shù)自動成為啟動入口函數(shù)
;; 指定入口模塊,`-main` 函數(shù)自動成為啟動入口函數(shù)
:main soul-talk.core
10. 請求入口
原理講解:請求入口
==前面講過,服務(wù)器接收到的請求,會首先送給就是套娃最外層的 handler ,即最后一個中間件返回的 handler ==
路由變量是一個 handler,一般要作為最內(nèi)層的 handler ,因此它會作為第一個中間件的參數(shù)
而最后一個中間件返回的handler ,就是最外層的 handler,即請求入口 handler
創(chuàng)建請求入口 Handler
將服務(wù)器請求、中間件、Handler 、路由組合成一個 Handler ,相當于一個成品套娃,命名為 app
==注意:路由總是作為鏈式調(diào)用的第一個參數(shù),即作為最內(nèi)層的原生 Handler==
(def app
(-> app-routes ;; 鏈式調(diào)用的第一個參數(shù)為路由 Handler
wrap-nocache
;; 自動重載中間件
wrap-reload
;; 常用中間件
(wrap-defaults site-defaults)))
11. 啟動服務(wù)
原理講解:服務(wù)器啟動方式
服務(wù)器啟動,只需要把請求入口 handler 傳給 jetty-run 函數(shù),并配置一定的參數(shù)即可。有兩種方式
從主函數(shù)啟動
在主函數(shù)中調(diào)用 jetty/run-jetty ,將服務(wù)啟動入口 Handler 傳給他即可
(ns soul-talk.core
(:require ......
[ring.adapter.jetty :as jetty]))
(defn -main []
(jetty/run-jetty app {:port 3000 :join? false}))
從 Ring 插件啟動
使用 lein-ring 插件,需要給插件指定服務(wù)啟動入口 Handler。在 project.clj 中添加以下代碼:
:plugins [[lein-ring "0.12.4"]]
;; 插件不通過 main 函數(shù)啟動,只需要指定一個入口 Handler
:ring {:handler soul-talk.core/app}
12. 測試運行
從主函數(shù) -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 函數(shù)啟動,只需要指定一個入口 Handler
:ring {:handler soul-talk.core/app}
;; 不使用插件的時候,程序仍然從 main 函數(shù)啟動
:main soul-talk.core
:profiles {
:user {
:dependencies []
:plugins [[lein-ancient "0.6.15"]]}})
src/soul-takl/core.clj
(ns soul-talk.core
(:require
;; 標準庫 io 函數(shù)
[clojure.java.io :as io]
;; 響應(yīng)簡化庫
[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]
;; 服務(wù)啟動函數(shù)
[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 "這是關(guān)于我的頁面"))
(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 ;; 鏈式調(diào)用的第一個參數(shù)為路由 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>