01 Clojure Web 程序基本架构

0. 流程图

01 - Clojure Web 程序基本结构.png

1. 创建项目

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 创建仓库,然后

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 处理完响应后,返回给服务器

创建自定义中间件 wrap-nocache

创建一个自定义的中间件 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. 添加静态资源

创建静态 index.html 模板页面

静态资源一般放置在 resources 下,这个路径可以直接被 io/resource 函数和其他 io 函数读取




    
    index


这是一个主页;


7. 创建自定义 Handler

再次强调,Handler 处理请求,返回响应

创建 home-handle

home-handle 中,直接读取 index.html 返回

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

;; 自定义 Handler,读取静态页面返回
(defn home-handle [request]
  (io/resource "index.html"))

注意:在 resources 目录 下的资源可以被程序读取,而且不需要加路径,但是不能被用户直接访问

响应的生成方式(原理讲解)

响应就是一个字符串或者一个哈希表 ,其生成方式有以下几种

  • 直接返回一个字符串
  • 直接返回一个哈希表
  • 直接读取静态页面返回
  • 利用 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 "your IP is"
                (:remote-addr request) 
                "")))

8. 路由

路由原理讲解

对路径字符串进行模式匹配,返回对应的 handler,并将这个 handler 绑定到路由变量上。因此路由变量就是一个 handler

使用 Compojure 路由库

依赖:[compojure "1.6.1"]

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

定义路由规则

定义路由规则。注意:最终的路由变量 app-route 其实也是一个 Handler

;; 创建路由规则,最终返回的是一个普通的 Handler
(def app-routes
  (routes
    (GET "/" request (home-handle request))
    (GET "/about" [] (str "这是关于我的页面"))
    (route/not-found "

Page not found

")))

9. 程序启动入口

需要告诉程序,哪个函数是程序启动函数。可以在配置文件中直接指定启动入口函数

;; 直接指定启动入口函数
:main soul-talk.core/foo

也可以只指定命名空间,则命名空间中的 -main 函数自动成为启动入口函数

;; 指定入口模块,`-main` 函数自动成为启动入口函数
:main soul-talk.core

10. 请求入口

原理讲解:请求入口

==前面讲过,服务器接收到的请求,会首先送给就是套娃最外层的 handler ,即最后一个中间件返回的 handler ==

路由变量是一个 handler,一般要作为最内层的 handler ,因此它会作为第一个中间件的参数

而最后一个中间件返回的handler ,就是最外层的 handler,即请求入口 handler

创建请求入口 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 区域
;; ************************************************

;; 自定义 Handler,读取静态页面返回
(defn home-handle [request]
  (io/resource "index.html"))



;; ************************************************
;; 路由 区域
;; ************************************************

;; 创建路由规则,最终返回的是一个普通的 Handler
(def app-routes
  (routes
    (GET "/" request (home-handle request))
    (GET "/about" [] (str "这是关于我的页面"))
    (route/not-found "

Page not found

"))) ;; ************************************************ ;; 中间件 区域 ;; ************************************************ ;; 自定义中间件:加入不缓存头信息 (defn wrap-nocache [handler] (fn [request] (-> request handler (assoc-in [:headers "Pragma"] "no-cache")))) ;; ************************************************ ;; 启动代码 区域 ;; ************************************************ (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




    
    index


这是一个主页;


你可能感兴趣的:(01 Clojure Web 程序基本架构)