01 Clojure Web 程序基本架構(gòu)

0. 流程圖

01 - Clojure Web 程序基本結(jié)構(gòu).png

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-defaults
  • site-defaults
  • secure-api-defaults
  • secure-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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容