流程图
01 - Clojure Web 程序基本结构.png创建项目
lein new soul-talk
配置 Git
初始化 Git 仓库
git init
设置 .gitignore
忽略项
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-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
配置 project.clj
在 project.clj
中修改以下内容
添加各种依赖库
: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
,可以将项目的依赖都升级到最新版本
指定程序启动入口函数
可以指定命名空间中的某个函数作为程序启动入口函数
;; 程序运行入口
:main soul-talk.core/foo
也可以只指定命名空间,则命名空间中的 -main
函数自动成为启动入口函数
;; 程序运行入口
:main soul-talk.core
配置中间件
一个中间件也是一个普通的函数,但是他接收一个 Handler ,返回一个 Handler
创建自定义中间件
创建一个自定义的中间件 wrap-nocache
,功能是在头信息中添加不缓存设置
;; 自定义中间件:加入不缓存头信息
(defn wrap-nocache [handler]
(fn [request]
(-> request
handler
(assoc-in [:headers "Pragma"] "no-cache"))))
引入 Ring 内置中间件
添加一个 Ring 内置的中间件 wrap-reload
,但是目前自动载入还不好使,必须等 Ring 插件配置好才好使
(ns soul-talk.core
(:require ......
[ring.middleware.reload :refer [wrap-reload]])) ;; 添加
引入默认常用中间件
依赖:[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]))
添加静态资源
创建静态 index.html
模板页面
在 resources
目录下新建 index.html
模板页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
这是一个主页;
</body>
</html>
创建自定义 Handler
一个 Handler 就是接收一个 Request ,返回一个 Response 。二者在 Clojure 中都以哈希表的形式出现。
在 Handler 中读取 index.html
作为响应模板
修改 home-handle
函数,直接读取 index.html
返回
(ns soul-talk.core
(:require ......
[clojure.java.io :as io]))
;; 自定义 Handler,读取静态页面返回
(defn home-handle [request]
(io/resource "index.html"))
注意:在
resources
目录 下的资源可以被程序读取,而且不需要加路径,但是不能被用户直接访问
在 Handler 中可以使用简化 Response 库
依赖:[metosin/ring-http-response "0.9.1"]
由于 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>")))
配置路由
使用 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 "<h1>Page not found</h1>")))
创建服务启动入口 Handler
将所有的 Handler 、中间件、路由,通过链式调用,合并成一个启动 Handler ,绑定到一个名字上。注意:路由总是作为链式调用的第一个参数
(def app
(-> app-routes ;; 链式调用的第一个参数为路由 Handler
wrap-nocache
;; 自动重载中间件
wrap-reload
;; 常用中间件
(wrap-defaults site-defaults)))
添加服务启动代码
从主函数启动
在主函数中调用 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}
启动服务
从主函数 -main
启动
lein run
从 Ring 插件启动
使用插件启动后,自动载入中间件才好使
lein ring server ;; 默认端口 3000
lein ring server 4000 ;;
lein ring server-headless ;; 不会打开浏览器
最终代码
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 "<h1>Page not found</h1>")))
;; ************************************************
;; 中间件 区域
;; ************************************************
;; 自定义中间件:加入不缓存头信息
(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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
这是一个主页;
</body>
</html>
网友评论