开发Web最致命的想法就是把什么网站都当成Google来设计。对于Web应用来说,过度设计和设计不足都是错误的。
务实而优秀的开发人员会考虑Web应用的上下文,不会增加任何不必要的复杂性。认真分析所有应用的非功能需求是避免构建错误的关键前提。
Compojure就是那种不妄想征服世界的Web框架。对于Web仪表板、操作监控,以及很多更加注重简单性和开发速度、而不是大规模扩展能力及其他非功能需求的简单任务来说,Compojure是非常理想的选择。从这种描述中你应该能猜出来,Compojure介于多语言编程金字塔的领域特定层和动态层之间。
在这一节我们会搭建一个简单的Hello World应用,然后讨论Compojure把Web应用串起来的简单规则。在用这些规则搭建示例程序之前,先介绍一个实用的Clojure HTML类库(Hiccup)。
如图13-6所示,Compojure构建在Ring框架之上,Ring框架是Clojure连接到Jetty Web容器的中间件。但使用Compojure/Ring并不需要对Jetty有多深入的了解。我们先用一个简单的Hello World作为Compojure的入门应用吧。
图13-6 Compojure和Ring
13.6.1 Hello Compojure
开始一个新的Compojure项目非常容易,因为Compojure跟Leiningen的工作流程自然融合。如果你还没装Leiningen,也没看第12章中的那一节,那你现在就应该去把这两件事做了,因为接下来的内容要求你熟悉Leiningen。
要开始一个新项目,只要执行一个普通的Leiningen命令:
lein new hello-compojure
在project.clj中可以轻松指明项目的依赖项。代码清单13-8显示了如何在project.clj文件中指定Hello World项目的依赖项。
代码清单13-8 简单的Compojure project.clj
(defproject hello-compojure /"1.0.0-SNAPSHOT/" :description /"FIXME: write description/" :dependencies [[org.clojure/clojure /"1.2.1/"] [compojure /"0.6.2/"]] :dev-dependencies [[lein-ring /"0.4.0/"]] :ring {:handler hello-compojure.core/app})
宏(defproject)
跟第12章那个很像,不过多了两个元数据。
:dev-dependencies
确保开发人员可以在开发时使用lein
命令。稍后我们讨论lein ring server
时你就能见到实例了。:ring
引入了Ring类库所需的挂钩。它将Ring特定的元数据映射为参数。
这个例子中给Ring传入了一个:handler
属性。看起来它希望得到hello-compojure.core
命名空间中的app
符号。我们来看看代码清单13-9中core.clj中对它的声明,以便找出它们是如何相互配合的。
代码清单13-9 Compojure Hello World中简单的core.clj文件
(ns hello-compojure.core (:use compojure.core) (:require [compojure.route :as route] [compojure.handler :as handler]))(load /"hello/")(defroutes main-routes ; (GET /"//" (page-hello-compojure)) //主路由定义 (route/resources /"//") (route/not-found /"Page not found/"))(def app (handler/site main-routes)) //注册路由
这种把关联信息和其他信息保存在core.clj中的惯例非常实用。当有URL请求时再加载一个包含对应函数(页面函数)的单独文件很简单。这确实只是一个为了提高可读性,简单实现关注点分离的惯例。
Compojure使用了一组规则,称为路由,来确定如何处理接入的HTTP请求。这些规则是由Compojure依赖的Ring框架提供的,它们既简单又实用。你可能已经猜出来了,规则GET/"//"
告诉Web服务器如何处理对根URL的GET
请求。我们下一节会对路由做更多的讨论。
为了完成这个例子的代码,还需要在src/hello_compojure目录中创建hello.clj文件。在这个文件中要定义一个如下所示的页面函数(page-hello-compojure)
:
(ns hello-compojure.core)(defn page-hello-compojure /"<h1>Hello Compojure</h1>/")
这个页面函数是个常规的Clojure函数,它会返回一个字符串作为HTML文档的<body>
标签中的内容,而这个文档会作为响应的一部分返回给用户。
让我们把这个例子跑起来。在Compojure中这是个十分简单的操作。先确保所有依赖项都装上了:
ariel:hello-compojure boxcat$ lein depsDownloading: org/clojure/clojure/1.2.1/clojure-1.2.1.pom from centralDownloading: org/clojure/clojure/1.2.1/clojure-1.2.1.jar from centralCopying 9 files to /Users/boxcat/projects/hello-compojure/libCopying 17 files to /Users/boxcat/projects/hello-compojure/lib/dev
到目前为止一切都好。现在需要把它跑起来,可以用Ring提供的ring server
方法。
ariel:hello-compojure boxcat$ lein ring server2011-04-11 18:02:48.596:INFO::Logging to STDERR via org.mortbay.log.StdErrLog2011-04-11 18:02:48.615:INFO::jetty-6.1.262011-04-11 18:02:48.743:INFO::Started [email protected]:3000Started server on port 3000
这会启动一个简单的Ring/Jetty Web服务器(默认端口3000),以实现快速反馈。默认情况下,这个服务器会自动重载被修改的文件。
警告 需要知道开发服务器的重载是在文件这一层实现的。这意味着正在运行的服务器可能会因为重新加载页面导致其状态被冲掉(或更糟,被部分冲掉)。如果你怀疑发生了这种情况,并因此出现了问题,应该关掉服务器重新启动。启动Ring/Jetty很快,应该不会对开发时间有太大影响。
如果用浏览器访问开发机上的3000端口(或本机http://127.0.0.1:3000),应该会看到页面中显示出了“Hello Compojure”。
13.6.2 Ring和路由
我们来看看如何配置Compojure应用的路由。路由的定义应该能让你想到一种领域特定语言:
(GET /"//" (page-hello-compojure))
这些路由规则应当被看做匹配接入请求的规则。其构成方式非常简单:
(<HTTP 方法> <URL> <参数> <动作>)
HTTP方法,通常是
GET
或POST
,但Compojure也支持PUT
、DELETE
和HEAD
。如果要匹配这条规则,这个HTTP方法必须跟传入的请求相匹配。URL,请求对应的URL。如果要匹配这条规则,这个URL必须跟传入的请求相匹配。
参数,一个表示参数应该如何处理的表达式。很快我们就会对它展开讨论。
动作,与这条规则匹配时返回的表达式(通常表示为传入参数的函数调用)。
对这些规则的匹配按从上到下的顺序逐一比对,直到找到匹配项。Compojure会执行第一个匹配项的动作,表达式的值会作为返回文档<body>
标签中的内容。
Compojure中规则的定义很灵活。比如说,创建一个从URL中提取函数参数的规则非常简单。我们来改一下代码清单13-5中的Hello World路由:
(defroutes main-routes (GET /"//" (page-hello-compojure)) (GET [/"/hello/:fname/", :fname #/"[a-zA-Z]+/" ] [fname] (page-hello-with-name fname)) (route/resources /"//") (route/not-found /"Page not found/"))
这个新规则只匹配包含/hello/<名称>
的URL。其中的名称只能包含字母(大写、小写或大小写组合都行),这是由Clojure的正则表达式#/"[a-zA-Z]+/"
限定的。
如果匹配了这一规则,Compojure会以匹配的名称为参数调用(page-hello-with-name)
。函数定义非常简单:
(defn page-hello-with-name [fname] (str /"<h1>Hello from Compojure /" fname /"</h1>/"))
只有非常简单的应用才能用这种内联HTML,否则很快就会变成一种痛。好在有Hiccup模块,它为需要输出HTML的Web应用提供了很多实用的功能。马上我们就去看看。
13.6.3 Hiccup
要在hello-compojure应用中挂上Hiccup,需要做三件事:
- 在project.clj上加上依赖项,如
[hiccup /"0.3.4/"]
; - 再次运行
lein deps
; - 重启Web容器。
很好。现在我们来看看在Clojure内部怎么用Hiccup写出更好的HTML形式。
Hiccup提供的关键形式之一是(html)
。用它可以非常直接地编写HTML。下面是用Hiccup重写的(page-hello-with-name)
:
(defn page-hello-html-name [fname] (html [:h1 /"Hello from Compojure /" fname] [:p [:p /"Paragraph text/"]]))
现在这些嵌套格式的HTML标签看起来很像Clojure代码,所以把它放到代码里自然多了。(html)
形式以一个或更多的(标签)向量为参数,并且标签的嵌套深度不受限制。
接下来,我们会向你介绍一个稍微大一点儿的应用,一个给水獭投票的网站。