Clojure Web 堆栈 (Clojure Web Stack)
在上一个章节,我们已经构建了一个简单的应用程序。这让我们体验到了舒适的开发环境以及提供给了我们一个快速领略项目结构的机会。但是现在我们需要慢下脚步来花些时间来理解一下组件运行的细节。
由于Clojure社区崇尚的是简单、灵活的价值观,所以事情的解决往往会没有一个固定的解决规范。在Web堆栈这一块,几乎所有的组件都会有它的替代品。你可以挑选一个最适合你的来开发应用。在这本书中,我们将着重于 Ring/Compojure 来进行构建真实可用的应用。
在上一章节已经介绍了一个简单的应用:支持用户留言,并且可以查看到所有用户的留言。我们围绕着项目目录中的文件,来分析了他们各自所要达成的目的。然而,我们并没有非常仔细的去分析文件中的代码是什么。而在本章中,你将学习一些必要的背景知识,以至于充分理解我们的第一个应用。
由于Clojure Web 堆栈是建立在JAVA HTTP Servlet API的基础上的,应用程序可以部署在任何的Servlet容器之上,比如Jetty、GlassFish或者是Tomcat。
Clojure 的应用程序可以独立进行运行,也可以和已经存在的JAVA 应用部署在一个服务器上。(Clojure applications can be run standalone or can be deployed side-by-side with existing Java applications using an application server. 这句求更好的说法~我觉得翻译可能有偏差。)
还由于很多的云服务器运行的是JAVA虚拟机,你也可以将你的应用部署到像 Amazon Web Services, Google App Engine, Heroku 以及 Jelastic 这样的平台上面。
一个 servlet 接收到一个请求然后生成一个符合HTTP协议的相应的反馈。
这个API实现了一系列web应用需要的核心的方法:诸如cookies,sessions,还有像URL 重写这样的功能。然而,servlets是一个专门为JAVA进行设计的,而直接在Clojure下使用并没有提供最好的体验。
也不像其它的许多平台——比如Rails或者是Django这些,Clojure并没有一个整体的框架。与此相对的,你可以一起使用好几个库来构建一个应用,我们将集中介绍几个常用的进行web开发的库。
使用 Ring 进行路由请求
Ring的目标是将HTTP的细节进行抽象,以成为一个简单的模块化的API,用于构建大规模的应用。如果你使用Ruby或者Python进行web开发,那么它对应的就应该像是WSGI或者Rack这样的库。
由于Ring已经成为了构建Web应用的事实上的标准,围绕着它已经开发出了很多的工具和中间键。虽然在大多数情况下你并不会去直接使用Ring,但它会提升你对于设计的理解,也能帮助你更好的排查出自己应用中的故障。
Ring包含了四个基础的组件:处理器、请求、应答以及中间件(the handler, the request, the response, and the middleware),让我们一个部分一部分的看过来。
处理请求
Ring使用了Clojure Map来表示客户端的请求以及服务器端返回的相应内容。控制器的功用就是处理进入的请求。它接收一个请求Map然后返回一个响应Map。一个简单的Ring控制器可以是这个样子的:
(def handler [request-map]
{:status 200
:headers {"Content-Type" "text/html"}
:body (str "<html><body> your IP is: "
(:remote-addr request-map)
"</body></html>")})
你可以清楚的看到,这里接收了一个相当于HTTP请求的请求Map,然后返回了一个相当于HTTP响应的Map。然后Ring就会使用这个Map去生成一个HTTP Servlet的请求以及相应的对象。(Ring then takes care of generating an HTTP servlet request, and response objects from these maps.)
前面的处理程序主要的功能就是返回一个HTML字符串——其中带有IP地址,并且设置相应状态为200。作为一个常见的操作,Ring专门提供了一个API来辅助生成这样的应答:
(defn handler [request-map]
(response
(str "<html><body> your IP is: "
(:remote-addr request-map)
"</body></html>")))
如果你想要创建一个自定义的响应,你只需要写一个可以接收请求Map,并且返回一个你自定义的响应的函数就可以了。接下来让我们看看请求和响应的Map格式是什么样的。
请求和响应Map
请求响应的Map一般都包含服务器端口、URI、服务器地址和内容类型,还要加上主体部分(body)。在这个Map中出现的关键词都是和servlet API 以及 官方 HTTP RFC 进行对应的。
请求Map里有什么?
请求定义了下面的一些键。要注意的是下面像 :ssl-client-cert
这样的键并不是每个请求都必要的。
- :server-port —— 接收请求的服务器端口
- :server-name —— 服务器的IP地址或者是可以被解析的域名
- :remote-addr —— 客户端IP
- :query-string —— 请求的字符串
- :scheme —— 对于该协议的说明,可以是
:http
或者:https
- :request-method —— HTTP请求的类型,包括
:get
:head
:options
:put
:post
:delete
- :request-string —— 请求的字符串
- :contect-type —— 请求体的 MIME 类型
- :contect-length —— 请求的字节长度
- :character-encoding —— 请求的字符编码的名称
- :headers —— 一个包含请求头信息的Map
- :body —— 输入流中请求的主体部分
- :context —— 在不是根节点的情况下,可以被发现的上下文(The context in which the application can be found when not deployed as root.)
- :uri —— 在服务器上请求的地址,当
:context
可用的时候,会添加到它前面。 - :ssl-client-cert —— 客户端的ssl证书
除了这些Ring已经定义了的键之外,还可以使用中间件函数来扩展请求Map来添加一些特殊的键值。在这个章节的后面我们会演示怎么去做。
响应Map里有什么?
在响应的Map中只包含了3个键值用来描述HTTP响应:
- :status —— 响应的HTTP状态码
- :headers —— 返回到客户端的HTTP头
- :body —— 响应的主体
状态码是一个数字,它在HTTP RFC中有定义,最小的有效数值是100。
头是一个包括了HTTP头键值对的Map。头可以是字符串也可以是一组字符串组成,在这种情况下,一个键对应其中的一个字符串。
最后,响应的主体部分是一个字符串,它会原样返回到客户端上。如果它是一个序列,那么它就会将每一个元素的字符串发送到客户端。如果该响应是一个文件或者是输入流,那么服务器会将内容发送到客户端。
使用中间件(Middleware)添加功能
中间件允许将控制器(the handlers)包在函数中,以这样的方式来修改处理的请求。中间件还经常用来扩展基础的Ring控制器来适应你自己的应用。
中间件处理程序是一个接受已经存在的处理程序以及一些可选参数,返回一个新的添加了一些行为的处理程序。下面有一个例子:
(defn handler [request]
(response
(str "<html><body> your IP is: "
(:remote-addr request)
"</body></html>")))
(defn wrap-nocache [handler]
(fn [request]
(let [response (handler request)]
(assoc-in response [:headers "Pragma"] "no-cache"))))
(def app (wrap-nocache handler))
在这个例子中,封装函数接收一个处理函数,并且返回一个新的处理程序来作为处理程序。由于返回的函数定义在当前的包装函数之中,它可以直接在里面引用处理函数。当使用的时候,它将处理请求并且在响应Map中添加Pragma: no-cache
。
该包装函数被称之为「闭包」,因为它包装了操作函数以及参数,并且允许直接使用返回的函数。
刚刚使用的技术可以让我们创建出一些简单的方法,每一个都可以解决一个特定的问题。我们可以将他们方便的链接起来来实现复杂的逻辑。
适配器是什么
适配器处于底层的HTTP协议以及处理程序之间,他们提供着必要的配置,诸如端口映射,以及处理HTTP请求的解析成为请求Map,将响应Map构建成为HTTP的响应。你通常不需要直接通过适配器进行交互。我们不会再继续展开了。
年后第一个星期事情就是满满的啊!简直出乎意料,昨天休息了一天~只有今天有时间来继续完成没有完成的坑了~
翻译的可能还是一如既往的烂,但是我会找机会重新润色一下的。
相信自己并不会放弃,继续持续的更新下去的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。