基于mochiweb,REST风格的erlang Web容器。流程图见docs目录下的http-headers-status-v3.png。
它适合构建符合REST风格的面向资源的web应用,不适合那种需要HTTP长连接的应用(例如websocket,这也和webmachine基于的web服务器mochiweb支持websocket较弱有关,如果websocket很重要,有mislin和cowboy这两个web服务器可用,这些web服务器的比较见 这里,扯远了)。
不过,它也提供了流(stream)的数据传输,这类数据作为HTTP消息体(HTTP Body)进行传输,包括请求数据和响应数据(HTTP的请求body和响应body),对于比较大的数据可以裁成一节一节的数据流进行传输。见 Webmachine Streamed Body
hello, webmachine
webmachine提供了new_webmachine.sh脚本用于生成我们的webmachine应用骨架(一个标准的erlang OTP应用程序骨架、负责依赖管理和编译的rebar及其rebar配置,,make脚本,以及一个启动脚本),目录结构如下:
mywebdemo$ tree -L 2
.
├── deps/
│ ├── mochiweb
│ └── webmachine
├── ebin/
├── Makefile
├── priv/
│ ├── dispatch.conf
│ ├── log/
│ └── www/
├── README
├── rebar
├── rebar.config
├── src/
│ ├── mywebdemo.app.src
│ ├── mywebdemo_app.erl
│ ├── mywebdemo_sup.erl
│ ├── mywebdemo_resource.erl
│ └── mywebdemo.erl
└── start.sh
基于webmachine的web应用,用颜色标出开发相关部分,蓝色是这个web应用的erlang源代码,绿色为可执行脚本,红色为web应用的配置文件。深蓝色的是我们应用的web资源实现,理想情况下,不用修改其它erl模块,我们通过定义各种资源,并在dispatch.conf文件中设置好这些资源的映射实现我们的web应用。
所有的web请求日志都放在priv/log目录下,这些都是标准的http日志格式。
所有的静态文件都放在priv/www目录下,例如可以把我的web应用的favicon.ico放到这个目录下(这个简单的hello world应用现在还不能处理静态文件访问)
make之后用start.sh启动这个web应用,然后打开http://localhost:8000就可以看到hello world了
实际上这个骨架也可以用rebar生成,进入webmachine的目录后用rebar list-templates可以看到有一个wmskel的模版(就是WebMachine Skeleton的意思),在priv/templates目录下可以看到整个模版的内容,我们的web应用就是由此生成的。
基于webmachine的web应用
webmachine应用的结构如图所示:
当webmachine应用启动后其supervisor管理的进程树上挂着一个webmachine_router的genserver,这是负责对外(比如我们要开发的web应用)联络的接口。
我们要开发的web应用是通过一个典型的Erlang OTP应用实现:
一个OTP application及其app配置,
一个OTP supervisor,启动webmachine的工作在这个模块的init函数中进行:
启动后的web应用结构如果所示:
webmachine_mochiweb实际上是mochiweb_socket_server的注册名。
此外还包括一个控制模块mywebdemo,它有两个任务:
负责启动web应用依赖的其它应用(inets,crypto,mochiweb,webmachine);
最后启动这个application: mywebdemo。
此外这个模块还提供了stop功能
个人觉得mywebdemo这个模块有点像于C中main函数,在启动脚本start.sh中就直接使用这个模块启动我们的web应用。
不过我们这个由脚本自动生成的web应用骨架其实可以不必太过关注(不过HTTP服务的端口8000是写死在mywebdemo_sup模块中的)。
简单的理解是它负责将webmachine的HTTP处理逻辑嵌到mochiweb服务器中了。此后的HTTP请求都被webmachine接管过来,按照webmachine的逻辑(REST风格)进行处理,也就是docs目录下的那张大图(http-headers-status-v3.png)。
要关心的是如何处理资源。
实现REST风格的Web资源访问
自动生成的web应用骨架是不必修改的,需要做的是“添加”业务逻辑。
我们的web应用对外提供某种资源,对这些资源的处理也就是web应用的业务逻辑。每类资源的处理可以由一个erlang模块实现。
资源的映射、路由、或者dispatch
哪个请求对应哪个资源这种映射关系(也就是路由,或者说是dispatch)是在dispatch.conf文件中配置的。(当然也可以调用webmachine_router:add_route/1函数动态添加)
例如,自动生成的mywebdemo_resource模块是一个最简单的hello world文本资源。缺省的dispatch.conf是这个样子:
{[], mywebdemo_resource, []}.
这个tuple中的第一个元素是个list,对应着url中的路径,list中的元素是url路径中的token(也就是/隔开的字符串)。例如对于url:
http://localhost:8000/ab/c/c/dddd/e/f
对应的path tokens就是:
["ab", "c", "c", "dddd", "e", "f"]
对url的dispatch匹配规则:
list中的元素有三种类型:字符串,一个特殊的atom,'*'和其它普通atom
1. 字符串完全匹配
{[" ab"], mywebdemo_resource, []}.
只有http://localhost:8000/ ab/才能匹配mywebdemo_resource资源,
另外webmachine是区分大小写的,所以http://localhost:8000/Ab不会匹配这个资源。
2. 通配符匹配
如果
{["ab", '*'], mywebdemo_resource, []}.
现在这些url
http://localhost:8000/ab/ c
http://localhost:8000/ab/ cd
http://localhost:8000/ab/ c/d
http://localhost:8000/ab/ c/d/e
...
都能匹配资源了,注意'*'匹配了所有的末尾的path token(红色部分)。
对于这个dispatch
{[ "ab", "c", "d", '*'], mywebdemo_resource, []}.
只有这类url匹配了
http://localhost:8000/ ab/c/d/ e
当然'*'也可以在中间,例如这个dispatch:
{["ab", '*', "d", '*'], mywebdemo_resource, []}.
路径第一个token是ab,第3个是d的url都匹配这个资源。
'*'在中间与在末尾有一个很微妙的区别。如前所述,在末尾的'*'匹配一系列的path tokens,在中间的'*'只匹配一个path token。例如
{[ "ab", '*', "cd", '*'], mydemo_resource, []}.
对于这个url
http://localhost:8000/ ab/ c/pi/ cd/ ee/ff
中间的'*'是没法匹配"c" 和"pi"这两个path tokens的,当然最后的'*'没有这个限制。
3. 带名字绑定的统配
普通atom用在程序中,用于与路径token字符串建立映射。简单的讲这是个更高级的'*',它提供了路径的名字绑定:'*'是一种万能匹配,但是有时候我们想知道匹配的那部分path token怎么办,这时候名字绑定就可以用上了。举个例子,下面的atom是foo,对应的是ab之后紧跟的路径token,
{["ab", foo, "d", '*'], mywebdemo_resource, []}.
在程序中通过
V = wrq:path_info( foo, ReqData)
就可以得到url中foo对应的路径token,例如url是
http://localhost:8000/ab/ werqwerqw/d/
那么V就是"werqwerqw"
资源的实现
mywebdemo_resource是资源的缺省实现模块,内容如下
-module(mywebdemo_resource). -export([init/1, to_html/2]). -include_lib("webmachine/include/webmachine.hrl"). init([]) -> {ok, undefined}. to_html(ReqData, State) -> {"Hello, new world", ReqData, State}.
更多见 这里,比较枯燥。
对资源的访问
对资源的访问流程详见 http://wiki.basho.com/images/http-headers-status-v3.png
REST方式通过HTTP方法控制对资源的访问方式:创建(PUT),获取(GET)、修改(PUT/POST)还是删除(DELETE)资源。资源如果要支持以上方式就要自己提供相应的逻辑实现。
在REST之前,比较常见、也是比较直接的做法是,用户请求来之后,通过分析请求头的方法(即HTTP Method),调用对应的函数,(一个典型的例子如JEE的servlet,有对应的doGet,doPost等方法)。
但是webmachine不完全是通过HTTP Method分派处理的,它是通过content_types_provided和conttent_types_accepted提供对内容的处理方式。
前者指定对应的资源内容生成方式。也就是说根据HTTP客户请求的Accept消息头(HTTP Request Header)判断提供什么样的资源给用户。这种方式被称为Content negotiation
对应着资源的访问控制HTTP方法(GET)
后者通过HTTP客户请求的Content-Type消息头判断用户提供的数据是什么MIME类型的。
常用的对资源的访问控制HTTP方法(PUT/POST 等)都被分派到这个函数的处理上了。
DELETE方法倒是有对应的delete_resource
参考
http://wiki.basho.com/Webmachine-Quickstart.html