• 实战MochiWeb


    MochiWebmochibot.comBob Ippolito贡献的开源项目[在这里有一个介绍它的Slide]。

    MochiBot.com 提供 Flash 内容的访问统计和用户跟踪服务(大致上,可以理解为针对 flash 的 google Analytics 服务),他们在 mochiweb 之上构建了一个定制化的 web server ,并通过这个 web server 获取用户的访问数据(在这一点上有点象 Erlana 项目)。可以想象,这个定制的 web server 需要很高的并发支持,精简和牢固的底层架构,以及对于 http 协议的完备支持(乃至对于 socket 的直接操控)。如果可以的话,最好还有更为精简的 API ,易于定制的 URL 扩展方式,以及易于理解的底层框架。幸运的是,这些 mochiweb 都已经提供,而且还是开源的。

    需要说明的是,相比 yaws / inets httpd 而言,它的目标并不是 apache 之类的软件,它并不是一个完整的 web server (没有cache等机制,因而也不做任何加速动作),它只是一个实现 web server 的工具包(这也就意味着,它直接通过代码来扩展,你可以在它的基础上做任何事)。正因为此,在“需要定制 Web Server”的情况下,它成为一个非常不错的选择(比如,配置在 enginx 的后面,专门用于动态内容的生成)。在 erlang 的世界里,有几个项目已经开始转而使用 mochiweb 。

    下面是对这个项目代码的一些粗浅实战。

    首先遵循它的提示,通过svn获取代码:

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

    获得的文件和目录结构如下:

    deps  ebin     LICENSE   priv    scripts  support
    doc   include  Makefile  README  src

    注:大写字母开头的是文件,小写字母开头的是目录。这是一个相当标准的 Erlang 项目目录结构,其 Makefile (用到 support 目录的 make 包含文件)非常值得借鉴(而且也有简化这一借鉴步骤的办法,后面会提到)。

    这是一个纯粹的 Erlang 项目,并不涉及其它语言写的模块,照老规矩,直接 make :

    make

    注:如果你和我一样,仍在 R11* 上工作,那么 make 会在 edoc 的步骤中失败,这是因为 R11* 的 edoc 工具存在 bug 无法正确处理 mochiweb 用到的Parameterized module 语法,不用管它,并不影响后续使用。

    make 完成之后,要怎么试运行呢?这就涉及我们上面提到的“借鉴”工作。因为 mochiweb 是设计用来作为一个完整项目的一个基础部分,也就是说,它只是一个骨架(或者如作者所说的toolkit),在你 make 完之后,什么也干不了,除非你对它进行定制化编码,完成这个 web server 。好在它自己已经提供了工具来简化这一步骤:

    escript scripts/new_mochiweb.erl test

    new_mochiweb.erl 是一个 EScript 脚本,它负责从 mochiweb 中拷贝使用 mochiweb 所必须文件和目录,形成你的新项目的“骨架”(概念上有点类似于 rails 的自动生成代码)。上面的命令生成了名为 test 的项目,会在当前目录建立名为 test 子目录(还可以使用 escript scripts/new_mochiweb.erl test testdir 将新建立的项目放在 testdir 目录中)。上面的命令生成了一些文件,我加了注释:

    ./test/ 项目目录
        Makefile Make文件
        start.sh 启动脚本
        start-dev.sh 开发模式下的启动脚本(开启代码重载机制)
    ./test/ebin/ 编译目录
    ./test/support/ Make支持文件目录
        include.mk Make包含脚本
    ./test/deps/ 依赖目录,包含mochiweb自身
    ./test/src/ 代码目录
        skel.app 实际名称为test.app,OTP规范的应用定义文件
        Makefile Make文件
        skel_web.erl 实际名称为test_web.erl,应用的web服务器代码
        skel_deps.erl 实际名称为test_deps.erl,负责加载deps目录的代码
        skel_sup.erl 实际名称为test_sup.erl,OTP规范的监控树
        skel.hrl 实际名称为test.hrl,应用的头文件
        skel_app.erl 实际名称为test_app.erl,OTP规范的应用启动文件
        skel.erl 实际名称为test.erl,应用的API定义文件
    ./test/doc/ 文档目录
    ./test/include/ 包含文件目录
    ./test/priv/ 项目附加目录
    ./test/priv/www/ 项目附加的www目录
        index.html 默认的项目首页

    是的,什么也不用改,在新生成的项目骨架中,一个可用的web服务器已经就绪:

    make
    ./start-dev.sh

    这会打开一个 erlang shell ,输出的信息表明在 8000 端口开了一个 web 服务,此时用浏览器访问 http://localhost:8000 (或者其它正确的地址)就能看到“MochiWeb running.”,这表明 mochiweb 配置正确,运行良好。注意,我们上面是用 start-dev.sh 来启动的,它打开了 reloader 特性。

    现在修改一下 test_web.erl 的代码,加点料。因为我们上面已经打开了 reloader 所以,不用关掉这个 erlang shell ,我们可以直接修改和编译,然后刷新就能看到效果(有点 PHP 编程的意思了)。把 test_web.erl 改成这样,看看会有什么情况发生:

    下载: test_web.erl

    %% @author author <author@example.com>
    %% @copyright YYYY author.
     
    %% @doc Web server for test.
     
    -module(test_web).
    -author('author <author@example.com>').
     
    -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" ->    %% 新增了 /timer 这个 URL,它是一个 HTTP Chunked 的例子
                        Response = Req:ok({"text/plain", chunked}),
                        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)}.
     
    %% 打印当前时间,间隔一秒,再在已经打开的 http 连接之上,再次打印,
    %% 这也就是所谓 HTTP长连接/ServerPush 的一种
    timer(Response) ->
        Response:write_chunk(io_lib:format("The time is: ~p~n",
                                           [calendar:local_time()])),
        timer:sleep(1000),
        timer(Response).

    编译之前,先访问一下 http://localhost:8000/timer ,是“Not found.”。此时,不要中断之前的 erlang shell 而是直接再次 make :

    make

    留意到之前打开的 erlang shell 上出现了这么一行:

    1> Reloading test_web ... ok.

    此时,再次访问 http://localhost:8000/timer (耐心些 HTTP chunked 获得的数据要积累到一定的字节浏览器才会显示),你会发现这是一个不会“下载结束”的页面,不断会有新的内容出现在下面。你也许可以利用这个特性实现传说中的“无刷新聊天室”。

    值得留意的是这样的代码:

    ...
    Req:ok({"text/plain", chunked}),
    ...
    Req:serve_file(Path, DocRoot)
    ...
    Response:write_chunk(io_lib:format("The time is: ~p~n",
                                  [calendar:local_time()])),
    ...

    我们这里是用 Req:ok(…) 而不是 request:ok(Req, …) 这在 Erlang 的代码中并不寻常,Req 是一个变量,通常这个变量的值是某个 atom 表明的是一个 module 的名称,但这里的 Req 显然不是这样。它是一个 “module 的实例”,这就是我们前面提到的“ Parameterized module 语法”的实际应用,它不仅意味着某个模块的名称,还意味着(初始化时)传给这个模块的一系列参数,它包装了与一个 request 相关的数据。应该说,这个语法更加简洁易懂。

    问题:

    1. 如果在此时,并不关闭正在不断“下载页面”的浏览器,在 test_web.erl 中将 timer 的部分注释掉,然后再次 make ,会发生什么?为什么?
    2. 找出 Req 在 mochiweb 的哪个模块中被初始化?如何被初始化?它实际上是由哪个模块来实现的?
    3. 解释 test_web.erl 的代码结构,各个部分都起什么作用?它是如何服务于每一个请求的?
    4. 如何在 test_web.erl 中直接访问 http 连接的 socket ?

    (实际上,这个例子只是一个 HTTP Chunked 的例子而已,你并不能依赖于 HTTP Chunked 来实现聊天室,这不是 HTTP Chunked 的问题,而是因为在现实的网络环境下,路由器有可能会自动断开连接时长超过某个值的连接。)

     

     





  • 相关阅读:
    【HDOJ】1811 Rank of Tetris
    【HDOJ】1518 Square
    日期类 Date
    RunTime
    System 系统类
    StringBuffer
    获取联系人列表的时候contact_id出现null的值
    String类
    object类
    eclipse使用的步骤
  • 原文地址:https://www.cnblogs.com/firmy/p/2703988.html
Copyright © 2020-2023  润新知