BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

使用Erlang和Yaws开发REST式的服务

| 作者 Steve Vinoski 关注 0 他的粉丝 ,译者 韩锴 关注 0 他的粉丝 发布于 2008年6月27日. 估计阅读时间: 22 分钟 | 都知道硅谷人工智能做的好,你知道 硅谷的运维技术 也值得参考吗?QCon上海带你探索其中的奥义

看过那张很出名的“Apache vs. Yaws”图么?是不是在考虑你也应该使用Yaws了?这些图给人的第一印象是,Yaws在可伸缩性上具有难以置信的巨大优势,它可以扩展到80000个并行的连接,而 Apache只接入4000个连接后就无法继续支撑了。人们对这些图的反应存在着明显的分化,一种声音说“这些图不太可能是准确的”或者“他们一定没有正确地配置Apache”;另一种声音则完全相反,“Wow,我要尝试一下Yaws!”

无论你是否相信上面的Yaws对比图,Yaws的确是一个可靠的Web Server,可以处理动态内容。Claes Wikström使用Erlang开发了Yaws,“另一个Web Server(Yet Another Web Server)”。Erlang是一种编程语言,特别用于支持长时间运行的、并发的、高可靠的分布式系统。(要学习更多关于Erlang的知识,可以去看那本很精彩的“Programming Erlang”,它的作者是Erlang语言的创建者——Joe Armstrong。)Yaws的灵活性和Erlang的多种独一无二的特性相结合,使得它们成为了一个不可忽视的REST式的Web服务平台。如果你处理的是静态页面,去试试lighttpd或者nginx吧,但是如果你在写动态的、REST式的Web服务,那么Yaws是绝对值得尝试的。在这篇文章中,我将讲述我在使用Yaws和Erlang开发Web服务中的一些经验。

Yaws基础

Yaws提供了若干种处理动态Web内容以及支持REST式的Web服务的方法:

  • 在静态页面中嵌入Erlang代码。通过这种方法,你可以将...标签内的out/1函数直接嵌入到静态页面中。该函数包含了Erlang代码。这样的文件要以.yaws 为扩展名,从而通知Yaws处理它,并将...标签替换为执行out/1函数的结果,这正是页面应该包含的。在Erlang的术语中,out/1是元数(arity)1的函数,例如,某个带有一个参数的函数。这个参数应该是一个Yaws arg记录(record),这是一种特殊的数据结构,Yaws使用它将接收到的请求的细节传递给处理它们的代码。例如,一个arg记录可以提供请求URI、请求头、POST 数据等信息。

  • 应用程序模块(appmod)。由于Yaws的appmod,应用程序代码可以控制URI。在前面描述的方法中,Erlang代码被嵌入到静态文件中了,而这些文件的URI是由它们的路径相对于Web Server的文档根决定的。然而,有了appmod后,应用程序就会控制URI的含义,这些URI通常不会与任何文件系统上的工件有联系。Appmod 基本上都是一个导出out/1函数的Erlang模块。这些模块要在Yaws配置文件中进行配置,来关联一个URI路径元素。如果一个请求中包含了某个已注册的appmod所关联的路径元素,Yaws会调用这个模块的out/1 arg记录。模块的out/1函数可以继续解释URI剩下的部分,以此来解释请求和响应目标的具体资源是什么。

  • Yaws应用程序(yapp)。appmods通常仅仅是单一的Erlang模块,Yaws yapp与此不同,它是全功能的应用程序。每个yapp都有它自己的文档根,都有它自己的appmod集。说得明确些,yapp就是Erlang/OTP 应用程序。OTP表示“Open Telecom Platform(开发电信平台)”,它是一系列历经考验的库和框架,它们为Erlang程序带来了强大的能力。OTP封装了很多构建分布式、事件驱动、高可用性系统的模式和方法。Erlang/OTP已经在现实世界中获得了证明,它们可以被用在不同的电信系统中,例如,某些系统宣称它们的宕机时间每年不过几毫秒而已。

上述这三种方法(它们的细节可以在Yaws站点上找到)都可以有效地应用在REST式的Web Service中。具体情况就要依赖于Service本身的特征了。但是根据我的经验,yapp和appmod是最好用的,因为它们提供了对Web应用程序的最大控制。

REST式的设计

既然我们打算要开发REST式的Web服务,那么首先了解一下REST的相关细节。REST的全称是“表象化状态转变(Representational State Transfer)”。Roy T. Fielding博士在他的论文中首次提出了“REST”这个术语,用它来描述一个适用于高可扩展性分布式系统(比如Web)的架构风格。HTTP本质上就是REST的一个实现。术语“表象化状态转换”是指REST式的系统通过在请求和响应之间交换资源状态的表象,来完成各种操作。例如,对于一个典型的从HTTP GET获得的Web页面来说,它就是Web资源的一个HTML表象,通过URI来标识,并由GET来触发。

开发REST式的Web服务需要注意下面几点:

  • 资源与资源标识符

  • 每种资源支持的方法

  • 数据在客户端与服务端之间交换所使用的格式

  • 状态码

  • 每个请求和响应的HTTP头

让我们把目光集中在Yaws和Erlang中,逐一地看看上面列出的几个问题。

资源标识符

设计REST式的Web服务时,需要你考虑组成服务的资源,比如如何最佳地标识它们、其中一个如何与另一个相关联。REST式的资源由URI来标识。通常,资源都拥有一个与它们自身相关的URI,同时共享一个公共的路径元素。例如,在一个基于Web的Bug追踪系统中,所有 “Phoenix”项目(一个虚构的项目)中的bug都可以在http://www.example.com/projects/Phoenix/bugs/下找到,只要指明一个bug号就可以了,比如bug 12345的URI可能就是http://www.example.com/projects/Phoenix/bugs/12345/。REST式的资源还能够提供自身状态表象内部的其他资源的URI。对于一个获得特定资源状态的用户,可以使用这个返回的URI(包含在状态表象中)来导航到整个Web应用中的其他部分上。

在Yaws中,arg记录指定了请求的URI,使用yaws_api模块提供的request_url方法可以很容易地获得它:

out(Arg) ->
Uri = yaws_api:request_url(Arg),
Path = string:tokens(Uri#url.path, "/"),

一旦你得到了请求URI,那么可以像上面那样很方便地对请求路径切词,这要按照“/”进行分割就可以了。切词后可以得到路径元素的列表,它的起点是appmod的根节点。例如,假设我们将一个appmod绑定到“projects”路径元素上,完整的URI是http://www.example.com/projects/。如果一个请求URI的前缀是前面的URI,那么appmod的out/1函数从中获取一个分离的路径元素列表,代表了请求的目标资源。例如,一个URI为 http://www.example.com/projects/Phoenix/bugs/的请求,在执行过上面的一段代码后,Path 变量将保存下面的路径元素的列表:

["projects", "Phoenix", "bugs"]

分离URI的好处在于它可以简化后面进一步的转发工作,这得益于Erlang的模式匹配能力。例如,我们可以写一个函数,比如是out/2,像下面这样定义它的函数头,它就可以处理这种特殊的URI了:

out(Arg, ["projects", Project, "bugs"]) -> 
% code to handle this URI goes here.

这个out/2函数可以处理在所有已知的项目中,所有与bug列表相关的请求。Project变量会在方法体中出现,它的值被设置为正在请求的项目的名称。支持额外的URI同样非常简单:为out/2函数添加更多的变量。如果你不喜欢out这个函数名,可以换成任意的,因为Yaws框架不会直接调用它们。

注意,正确地定义资源URI可以产生巨大的好处。利用appmod和yapp,可以非常容易地拥有一个巨大的、丰富的URI空间,因为无论是将不同的 appmod绑定到不同的URI路径元素,还是转发请求,都相当简单。Erlang的模式匹配降低了处理处理不同URI请求的难度。这与传统的非REST 式的服务在处理这种问题时的笨拙形成鲜明的对比,它们为所有服务都提供一个相同的URI。一般这个URI会指向一个脚本,它通过请求体自身的信息或者 URI查询字符串的信息来判断将请求实际转发到哪里。这种基于传统技术的URI看上去似乎有永无止境的参数,与此相比,前面所示的基于 Erlang/Yaws转发技术的URI要清晰的多。

资源方法

Web客户端可以调用的Web资源上的方法是由HTTP的动词定义的,主要包括GETPUTPOSTDELETE。但是,有些资源只能支持这些动词的一部分。当你在设计Web服务时,需要确定每种资源都支持哪些方法,记住,RFC 2616定义了每种HTTP动词期望的语义。

Yaws可以在http_request记录中找到请求方法,它可以通过arg记录很容易地获得:

Method = (Arg#arg.req)#http_request.method

它返回表示请求方法的Erlang atom,可以将它添加到模式匹配的转发方法中去。我们可以为out函数添加一个新的参数来包含请求的方法 ,于是就有了out/3

out(Arg, 'GET', ["projects", Project, "bugs"]) ->
% code to handle GET for this URI goes here.

这个out函数的变体只能够处理对每个项目的bug列表的GET请求。另一个变体可以处理POST,也许通过它来在列表中添加新的bug。如果希望只允许GETPOST请求,而拒绝其他的动作,可以再为这个URI编写一个统一处理的函数:

out(Arg, 'GET', ["projects", Project, "bugs"]) ->
% code to handle GET for this URI goes here;
out(Arg, 'POST', ["projects", Project, "bugs"]) ->
% code to handle POST for this URI goes here;
out(Arg, _Method, ["projects", _Project, "bugs"]) ->
[{status, 405}].

在此,GETPOST以外的方法将会匹配第三个变体,它会返回HTTP状态码405,意味着“method not allowed”。由于MethodProject变量并未在方法中使用,所以在它们前面加下划线可以关闭编译器对此发出的警告。

就像URI转发一样,Erlang模式匹配可以让开发者很容易地将不同的HTTP动词转发到不同的函数上。

表现格式

在设计REST式的Web服务时,你需要考虑每个资源支持哪些表象。比如,Web服务资源通常都支持XML或者JSON表象。Erlang提供了xmerl library,可以创建和读取XML,Yaws提供了一个方便的JSON模块。这些都非常好用。

你可以通过请求的Accept头来判断客户端更喜欢哪种表象。这个头可以在headers记录中获得,并可以在arg记录中使用:

Accept_hdr = (Arg#arg.headers)#headers.accept

如果资源支持多种表象,你可以检查这个头,判断客户端是否指定了它希望的表象类型。如果客户端没有发送Accept头,上面代码中的Accept_hdr变量将被设置为atom undefined,你的资源可以提供任何它认为最佳的表象。如果Accept头不是空的话,服务可以解析Accept_hdr变量来判断发送哪一中资源。如果资源无法满足客户请求的表象,服务将返回HTTP状态码406,这意味着“not acceptable”,同时返回一个包含可接受格式列表的boby

case Accept_hdr of
undefined ->
% return default representation;
"application/xml" ->
% return XML representation;
"application/json" ->
% return JSON representation;?
_Other ->
Msg = "Accept: application/xml, application/json",
Error = "Error 406",
[{status, 406},
{header, {content_type, "text/html"}},
{ehtml,
[{head, [], [{title, [], Error}]},
{body, [],
[{h1, [], Error},
{p, [], Msg}]}]}]
end.

上面的Erlang代码首先检查 Accept_hdr的值,确定是否为application/xml或者application/json。如果是这两者之一,资源将返回一个适当的表象;如果不是,代码将返回HTTP状态码406,同时还有一个HTML文档,指明资源能够支持的表象类型。

处理预期表象的另一种方法是(你已经猜到了)将它做为另一个参数,添加到out函数中。利用这种方法,Erlang模式匹配能够确保我们的请求可以被转发到正确的函数,请求中将包含URI/method/representation。这样可以避免出现像上面那样由于case语句导致的杂乱无章的处理程序。

顺便提一句,这个例子中也出现了Yaws的ehtml类型,它是一系列的Erlang术语之一,代表一种HTML的表现方式。我发现使用ehtml是相当自然的事情,因为它后面直接是一个HTML结构体,但是它更加紧凑,而且你在编写HTML语义时,避免了很多匹配标签带来的乏味和错误。

状态码

REST式的Web服务必须返回一个正确的HTTP状态码,它们是由RFC 2616指定的。使用Yaws能够很容易地返回正确的状态:只要在out/1函数的结果中包含一个status tuple就可以了。如果你的代码没有显式地设置状态,Yaws会为你设置一个200状态,表示成功。

HTTP头

Yaws也可以很容易地获得请求头和设置回复头。我们已经看到了一个从头记录中获得Accept头的示例;获取其他请求头的方法完全一样。设置回复头只需要在回复中放置一个header tuple就可以了,如下所示:

{header, {content_type, "text/html"}}

上面的代码会将Content-type头设置为“text/html”。类似地,在前面的例子中,我们返回405状态表示“method not allowed”错误,我们也应该包含下面的头:

{header, {"Allow", "GET, POST"}} 

是Appmod,还是Yapp?

到目前为止,我们已经看到了Yaws和Erlang是如何方便地解决REST式的 Web服务中需要面对的一些关键问题的。还有一个问题,我们应该选择appmod,还是yapp呢?答案依赖于你的服务要做的事情。如果你编写的服务必须与其他后端的服务交互,那么yapp可能是最好的选择。因为它们是彻头彻尾的Erlang/OTP应用程序,它们通常都有初始化和终结函数,用来创建和关闭到后端的连接。比如,如果你的yapp是一个Erlang/OTP gen_server, 你的init/1函数可以创建gen_server框架提供给你的状态,并允许你对它进行修改。每次接收到外部到服务器的请求后都会调用init/1。另外,使用yapp的同时也可以使用appmod,因此在这两者间做选择并不是非常关键。最后,yapps可以加入到Erlang/OTP的监管树(supervision tree)中,这样监控进程会监控yapp程序,一旦失败会将它们重新启动。Erlang系统之所以可以长时间稳定地运行,监管树在其中扮演了一个很重要的角色。

这篇文章是为基于后端,而非关系数据库的REST式的Web服务量身定做的。如果你正在编写传统的、基于关系数据库的Web服务,你应该试用一下专为这类Web服务准备的Erlyweb,它也是基于Yaws和Erlang的。

结论

编写REST式的Web服务的另一个重要方面是选择恰当的编程语言。这些年来,用不同的编程语言开发的各种服务框架令人眼花缭乱,大多数很快就从人们的视野中消逝了,因为它们不能很好的解决真正的问题。Yaws和Erlang并不是专门用于提供REST式的服务的框架,不过它提供的功能比很多用其他语言开发专用于REST的框架更合适这个领域的开发。

尽管这篇文章必然无法深入Yaws、Erlang和REST式的Web服务的细节,不过希望它能够涉及到重要的主题,通过最少量的代码,提供一个解决这些问题的思路。根据我的经验,使用Yaws和Erlang构建Web应用程序非常简单,最终的代码也容易阅读、维护和扩展。

作者简介

Steve Vinoski是IEEE和ACM的成员。在过去20年里,他已经独自或与他人合作编写超过了80篇文章,各种专栏,以及一本关于分布式计算和整合的专著,在过去的6年里,他负责IEEE Internet Computing杂志的“Toward Integration”专栏。你可以给他发送邮件vinoski@ieee.org,或者访问他的blog:http://steve.vinoski.net/blog/

查看英文原文RESTful Services with Erlang and Yaws

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

很好很强大。并行时代我们应该关注此类技术语言 by xiao deshi

回复为什么需要标题,呵呵。有点不失所以然

不知道这样的技术在国外是否已经有成功的案例。 by yu haojun

不知道这样的技术在国外是否已经有成功的案例。但是在国内还只是技术人员讨论的前沿技术,不知道有没有成功案例。

Re: 不知道这样的技术在国外是否已经有成功的案例。 by Zoom Quiet

OTP !

Re: 很好很强大。并行时代我们应该关注此类技术语言 by Wang Shell

我觉得真得很不错。

Yaws很难用 by Wang Shell

不知道该怎么配置。

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

5 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT