:MochiWeb一些资料的链接

转自:http://veniceweb.googlecode.com/svn/trunk/public/daily_tech_doc/mochiweb_20091030.txt

MochiWeb项目主页:

http://code.google.com/p/mochiweb/





1. 网络资源

1.1 实战Mochiweb

http://erlang-china.org/start/mochiweb_intro.html



1.2 (译)用Mochiweb打造百万级Comet应用,第一二三部分

http://idisc.javaeye.com/blog/267028

http://idisc.javaeye.com/blog/270076

http://idisc.javaeye.com/blog/273074

(原文)

http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-1

http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-2

http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-3



1.3 MochiWeb的设计

http://www.javaeye.com/topic/348379



1.4 Comet web chat(with MochiWeb)

一个老外使用MochiWeb构造webchat的例子

http://yoan.dosimple.ch/blog/2008/05/15/

这篇文章的中文介绍

http://erlang-china.org/misc/facebook_comet_in_mochiweb.html



2. 安装 & Example:

2.1 svn checkout http://mochiweb.googlecode.com/svn/trunk/ mochiweb-read-only

2.2 进入到mochiweb的目录, 直接make.

2.3 escript scripts/new_mochiweb.erl basicweb  (生成的新项目在当前目录的basicweb目录下)

    escript scripts/new_mochiweb.erl basicweb /home/woomsgadmin/web (指定新项目的存放路径)

    new_mochiweb.erl是一个escript脚本, 负责从mochiweb中拷贝使用mochiweb所必需的文件和目录,

    形成新的项目的骨架,上面的命令生成了basicweb项目.



    项目的目录结构:

    ./basicweb/ 项目目录

                Makefile              Make文件

                start.sh              启动脚本

                start-dev.sh          开发模式下的启动脚本(开启代码重载机制)

    ./basicweb/ebin/                  编译目录

    ./basicweb/support/               Make支持文件目录

                   include.mk         Make包含脚本

    ./basicweb/deps/                  依赖目录,包含mochiweb自身

    ./basicweb/src/                   代码目录

                   basicweb.app       OTP规范的应用定义文件

                   Makefile           Make文件

                   basicweb_web.erl   应用的web服务器代码

                   basicweb_deps.erl  负责加载deps目录的代码

                   basicweb_sup.erl   OTP规范的监控树

                   basicweb.hrl       应用的头文件

                   basicweb_app.erl   OTP规范的应用启动文件

                   basicweb.erl       应用的API定义文件

    ./basicweb/doc/                   文档目录

    ./basicweb/include/               包含文件目录

    ./basicweb/priv/                  项目附加目录

    ./basicweb/priv/www/              项目附加的www目录

                   index.html         默认的项目首页

 

2.4 在新的项目中, 直接make

2.5 ./start-dev.sh启动新的项目.      



3. 演示一个comet的例子,使用HTTP-CHUNKED的例子, Server端可以一直推送数据到客户端:

重点:

(下面使用Req:ok/1这是典型的参数化模板的例子)

<1> 使用 Req:get(path)获得当前的访问路径Path

<2> 使用 Req:get(method)获取当前的方法: GET, HEAD, POST

<3> 使用 Response = Req:ok({"text/plain", chunked})来产生一个chunk-response

<4> 使用 Response:write_chunk(Data)来发送响应到客户端, Data是发送给客户端的内容.



-module(basicweb_web).

-export([start/1, stop/0, loop/2]).



%% External API



start(Options) ->

    {DocRoot, Options1} = get_option(docroot, Options),

    Loop = fun (Req) ->

                   ?MODULE:loop(Req, DocRoot)

           end,

    mochiweb_http:start([{name, ?MODULE}, {loop, Loop} | Options1]).



stop() ->

    mochiweb_http:stop(?MODULE).



loop(Req, DocRoot) ->

    "/" ++ Path = Req:get(path),

    case Req:get(method) of

        Method when Method =:= 'GET'; Method =:= 'HEAD' ->

            case Path of

		"timer" ->

		    Response = Req:ok({"text/plain", chunked}),   

		    handle_timer(Response);

                _ ->

                    Req:serve_file(Path, DocRoot)

            end;

        'POST' ->

            case Path of

                _ ->

                    Req:not_found()

            end;

        _ ->

            Req:respond({501, [], []})

    end.



%% Internal API



get_option(Option, Options) ->

    {proplists:get_value(Option, Options), proplists:delete(Option, Options)}.



handle_timer(Response) ->

    Response:write_chunk(io_lib:format("time is: ~p~n", [calendar:local_time()])),

    timer:sleep(1000),

    handle_timer(Response).



测试:

当用户输入http://localhost:8000/timer的时候,

页面显示(每隔一秒钟打印一条信息):

time is: {{2009,12,9},{21,18,31}}

time is: {{2009,12,9},{21,18,32}}

time is: {{2009,12,9},{21,18,33}}

time is: {{2009,12,9},{21,18,34}}

time is: {{2009,12,9},{21,18,35}}



4. 分析mochiweb的内部结构::

总结:

a. 如果响应静态文件,通过Req:server_file(Path, DocRoot)来发送

b. 如果生成动态内容,通过Req:respond({Code, ResponseHeaders, Body})来发送(文本和二进制都可以)

c. 如果是chunked响应,第一次调用Req:respond()后返回一个Response对象(参数化模块mochiweb_response的一个实例),

   以后的响应数据通过这个Response对象发送给客户端,调用Response:write_chunk(Data)来发送后续响应的数据.



补充:

mochiweb内部,每个HTTP请求一个Erlang Process来处理.





详细介绍:

<1> Web服务器分为三步和mochiweb_request

a. 接收HTTP请求

b. 处理HTTP请求,生成响应内容

c. 发送响应



mochiweb_request是处理HTTP请求的核心部分,负责了第二和第三步的工作,

由于使用了参数化模板的技术,这意味着具备了OO的特性,它的每个实例化对象

代表一个用户请求,我们可以像操作OO那样处理HTTP请求,获取请求的信息,

生成响应内容等等.



<2> 通过Req直接发送给浏览器(Req是mochiweb_request的实例化对象).

Req:respond({Code, ResponseHeaders, Body})

Code是HTTP的状态码,例如200

ResponseHeaders是响应头

第三个参数是响应内容

a. 

HResponse = mochiweb_headers:make([]),

HResponse1 = mochiweb_headers:enter("Content-Type", "text/plain", HResponse),

Req:respond({200, HResponse1, "<html><head></head><title>Timer</title><body><h2>this is h2</h2></body></html>"});       

页面将显示(因为是text/plain):         

<html><head></head><title>Timer</title><body><h2>this is h2</h2></body></html>



b. 效果和a一样.

Req:respond({200, [{"Content-Type","text/plain"}], "<html><head></head><title>Timer</title><body><h2>this is h2</h2></body></html>"});      



c. 上面的例子也可以直接使用Req:ok{ContextType, Body},实质上内部也是调用Rep:respond/1返回状态码为200的HTTP响应.

Req:ok({"text/html", "<html><head></head><title>Timer</title><body><h1>this is h1</h1></body></html>"});

页面显示

this is h1



d. URL不存在的404错误可以使用Req:not_found():

其内部使实现是:

not_found() ->

    not_found([]).

not_found(ExtraHeaders) ->

    respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders], <<"Not found.">>}). %% Body使用Binary提高效率.



e. 如果是静态文件,如图片, 可以使用Req:server_file(Path, DocRoot)来返回

告诉此函数文件的Web根目录(doc_root)和相对路径(Path)就可以将指定的静态文件发给浏览器了

doc_root目录一般在配置文件中设置,这个目录下的所有文件都可以通过HTTP访问, 默认是yourweb/priv/www.



如果我的一张图片在Server端得存放路径是priv/www/image/test.jpg.

Req:serve_file(Path, DocRoot)              -> http://localhost:8000/image/test.jpg

或者

Req:serve_file(Path, DocRoot ++ "/image")  -> http://localhost:8000/test.jpg



<3> 通过Response模块对象将响应发送给客户端(本质还是调用Req:respond来发送).

Req:respond函数会返回一个mochiweb_response参数化模块实例对象(假设为Response),

Response实例对象包含有对应的Req实例对象。通过Response对象可以得到响应的相关信息

(如响应状态码,响应消息头,对应的Req对象),它还有一个 send函数可以将响应数据发还给浏览器

(它的实现其实还是调用Req对象的send函数进行的)。



Response之所以还要有send函数是为了发送 chunked数据(HTTP 1.1)的方便,在第一次响应完成后,

后继的chunk数据就可以通过最初返回的Response对象继续进行发送了,为此Response有个函数 

write_chunk()专门干这事,write_chunk检查了请求消息头中的HTTP 版本消息后就调用Response:send。



因此,响应内容最终都是由参数化模块mochiweb_request的respond/1函数发送的。而这个respond(...)

函数内部最后调用了Req:send(Data)函数将响应通过socket连接(调用gen_tcp:send)返还给浏览器,

这一发送过程又分成两个阶段:响应消息头(Headers)的发送和消息体(Body)的发送,这两步都是通过Req:send完成的





5. Req:get(XXX)方法解析

mochiweb中,每个client请求其构造一个 Req 对象, Req可以理解成 mochiweb_request 的一个参数化或实例化.

<1> Req:get(method) -> ‘OPTIONS’ | ‘GET’ | ‘HEAD’ | ‘POST’ | ‘PUT’ | ‘DELETE’ | ‘TRACE’.

获取Http请求的方式.

<2> Req:get(raw_path) -> String().

获取raw_path.比如 http://www.nextim.cn/session/login?username=test#p,那/session/login?username=test#p就是这个raw_path.

<3> Req:get(path) -> String().

获取path.比如 http://www.nextim.cn/session/login?username=test#p,那/session/login就是这个raw_path.

<4> Req:parse_qs() -> [{strng(), string()}].

获取get参数.比如 http://www.nextim.cn/session/login?username=test#p,则返回[{"username","test"}].

<5> Req:parse_post() -> [{strng(), string()}].

确保post数据类型为: application/x-www-form-urlencoded, 否则不要调用(其内部会调用Req:recv_body),返回值类似Req:parse_qs().

<6> Req:get(peer) -> string().

返回值为client的ip

<7> Req:get_header_value(Key) -> undefined | string().

获取某个header,比如Key为”User-Agent”时,返回”Mozila…….”

<8> Req:get_primary_header_value(Key) -> undefined | string().

获取http headers中某个key对应的主值(value以分号分割).

举例: 假设 Content-Type 为 application/x-www-form-urlencoded; charset=utf8,则

Req:get_header_value(”content-type”) 返回 application/x-www-form-urlencoded

<9> Req:get(headers) -> dict().

获取所有headers

说明: 返回结果为stdlib/dict 数据结构,可以通过mochiweb_headers模块进行操作.

举例: 下面代码显示请求中所有headers:

Headers = Req:get(headers),

lists:foreach(fun(Key, Value) ->

io:format(”~p : ~p ~n”, [Key, Value])

end,

mochiweb_headers:to_list(Headers)).

<10> Req:parse_cookie() -> [{string(), string()}].

解析Cookie

<11> Req:get_cookie_value(Key) -> string() | undefined

类似Req:get_header_value(Key)



6. Cookie的例子:

下面是一个读取和设置Cookie的例子:

<1> 读取Cookie

Req:get_cookie_value(Key) -> string() | undefined

<2> 设置Cookie, 设置的cookie作为ResponseHeaders在Req:respond({Code, ResponseHeaders, Body})返回.

mochiweb_cookies:cookie(Key, Val, Options) 其中Key:string(), Val:string()



功能: 利用Cookie记录页面的访问次数

http://localhost:8000/timer的时候,第一次返回

Key: undefined,

然后是

Key: Number     (其中Number是你访问这个页面的次数)



loop(Req, DocRoot) ->

    "/" ++ Path = Req:get(path),

    case Req:get(method) of

        Method when Method =:= 'GET'; Method =:= 'HEAD' ->

            case Path of

		"timer" ->

		    CookieKey = Req:get_cookie_value("key"),   %% 读取Cookie 

		    CookieKey1 = case CookieKey of

				     undefined ->

					 1;

				     _ ->

					 list_to_integer(CookieKey) + 1

                                 end,

		    CookieKey2 = mochiweb_cookies:cookie("key", integer_to_list(CookieKey1), []),  %% 设置Cookie

                    Req:respond({200, [{"Content-Type","text/html"}, CookieKey2], io_lib:format("<h1>Key: ~p~n</h1>", [CookieKey])});                

                 _ ->

                    Req:serve_file(Path, DocRoot ++ "/image")

            end;

        'POST' ->

            case Path of

                _ ->

                    Req:not_found()

            end;

        _ ->

            Req:respond({501, [], []})

    end.





7. 重定向的例子: URL Redirect

loop(Req, DocRoot) ->

    "/" ++ Path = Req:get(path),

    case Req:get(method) of

        Method when Method =:= 'GET'; Method =:= 'HEAD' ->

            case Path of

		"login" ->

		    Req:respond({302, [{"Location", "home/user/qiang"}], ""});    %% 重定向到 http://localhost:8000/home/user/qiang

		"home/user/qiang" ->

		    Req:respond({200, [{"Content-Type", "text/html"}], "login success"});

                 _ ->

                    Req:serve_file(Path, DocRoot)

            end;

        'POST' ->

            case Path of

                _ ->

                    Req:not_found()

            end;

        _ ->

            Req:respond({501, [], []})

    end.





















                    

你可能感兴趣的:(Web)