http://blog.csdn.net/dp0304/article/details/6994435

Web服务器的基本工作大致分3步:

  1. 接收HTTP请求;
  2. 处理HTTP请求,生成响应内容;
  3. 发送响应

一、处理请求和发送响应

模块mochiweb_request可说是Mochiweb处理HTTP请求的核心部分,它总共负责了第2步和第3步工作。因此参数化模块mochiweb_request的实例不像它的模块名那样单纯:它还负责将请求的响应(通过Socket连接,调用gen_tcp:send)发还(response)给浏览器。这个模块是一个参数化模块。这意味着它具有基于对象(Object-based)的特点,它的每个实例化对象代表一个用户请求,用户可以像OO那样操作HTTP请求:获取请求信息,生成响应内容等。

对静态内容(如html文件,静态图片)来说,无非是读取文件系统中静态文件的内容作为响应消息体(Body)发送给客户端浏览器。

对动态内容来说,生成响应内容的业务逻辑是用户通过编程实现的:解析请求(包括URL路径和HTTP请求头),然后生成相应的响应内容。用户编写业务逻辑处理函数,然后将函数插入到mochiweb中。此函数带有一参数,该参数是参数化模块mochiweb_request的实例(假设为Req),在这个定制函数中可以通过这个Req实例对象获取浏览器请求的所有信息(包括URL路径和HTTP请求头),处理后的响应数据也通过Req实例提供的函数发还给浏览器,有好几种方式:

  1. 通过Req实例对象直接发还给浏览器:

    a) 通过这个Req实例对象的response/1函数直接发还给浏览器:
    Req:response({Code, ResponseHeaders, Body})
    这个函数接收一个tuple参数,参数的第一个元素是HTTP状态码(比如200),第二个元素是响应消息头列表(一个二元tuple,key和value都是字符串,如{“Content-Type”, “image/jpeg”}),第三个元素即为HTTP响应内容;如果是动态内容,采用response函数直接发还响应数据还是比较方便的。

    b) 如果是静态文件,通过Req的serve_file(…)函数可以直接将文件发还给浏览器,告诉此函数文件的Web根目录(doc_root)和相对路径就可以将指定的静态文件发给浏览器了。doc_root目录一般在配置文件中设置,这个目录下的所有文件都可以通过HTTP访问。

    c) 一些常见的例行响应:
    不存在的URL请求返回404错误,可以直接调用Req:not_found() 若一切正常,调用Req:ok({…, Body})直接返回状态码为200的HTTP响应
    b,c两种情况的内部其实还是调用的Req:response()

  2. 通过Response模块对象将响应发还给浏览器:

    Req:response函数会返回一个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的response/1函数发送的。而这个response(…)函数内部最后调用了Req:send(Data)函数将响应通过socket连接(调用gen_tcp:send)返还给浏览器,这一发送过程又分成两个阶段:响应消息头(Headers)的发送和消息体(Body)的发送,这两步都是通过Req:send完成的。

小结:对于程序员来说,他编写的服务器端处理HTTP请求的函数必须带有一个参数,这个参数代表了mochiweb_request参数化模块的一个实例对象。通过这个实例程序员可以得到HTTP请求的所有信息(包括路径、请求参数以及HTTP请求消息头),然后生成响应,他还可以通过该mochiweb_request实例对象将响应数据发还给浏览器。

  • 如果响应的是静态文件,可以通过Request:server_file()函数发送响应;
  • 如果响应是动态生成的内容,通过Request:response()函数发送响应数据(文本数据和二进制数据都可以);
  • 如果是chunk响应,第一次调用Request:response()函数发送数据后,该函数会返回一个Response实例对象(参数化模块mochiweb_response的一个实例),以后的响应数据可以通过这个Response实例对象(调用Response:write_chunk(Data))发送后续的响应数据。

二、Web服务器的业务处理逻辑如何嵌入到Mochiweb

业务处理逻辑被程序员编写成一个函数(函数式编程中函数也是一种数据,以下称为HttpLoop),处理函数HttpLoop是作为mochiweb启动时的参数之一进入Mochiweb,Mochiweb是这样启动的:

mochiweb_http:start([{loop, HttpLoop},...])  

Request实例对象最终要调用gen_tcp:send(Socket,…)将响应数据由socket连接发给浏览器,因此,Request实例对象应该持有HTTP请求的socket连接。这个目标是这样实现的: 
在启动过程中,mochiweb_http又对用户的HttpLoop函数进行了重新包装

Loop = fun (Socket) ->
    mochiweb_http:loop(Socket, HttpLoop)
end,

每个浏览器连接请求对应着一个Socket连接,新的Loop函数以此Socket作为参数,然后通过mochiweb_http:loop函数对Socket连接和用户自定义的HttpLoop函数进行了处理,简单的说,这个函数做了如下工作:

  1. 将Socket连接设置成能处理HTTP数据包 (inet:setopts(Socket, [{packet, http}]));
  2. 准备接收socket连接传来的数据(gen_tcp:recv(Socket,…));

当收到数据时:

  1. 将HTTP消息头数据提出(成为{HeaderName, HeaderValue}的tuple列表)
  2. 生成一个参数化模块mochiweb_request的实例对象,并将Socket连接、HTTP请求信息(路径、请求方法、HTTP版本)以及请求消息头列表包装到此实例对象中,
  3. 然后调用用户的HttpLoop对请求进行处理和响应(如前所述,处理得到的响应数据也在用户编写的HttpLoop函数中被发送给浏览器)
  4. 最后根据HTTP请求信息决定是简单的关闭Socket连接,还是清理一下Req对象并保持连接(例如对keep-alive,chunk等类型的HTTP请求,以及还没完成数据传送的HTTP请求),以便继续让HttpLoop函数进行处理,

这都是在headers函数中进行的,还是看mochiweb_http模块的代码:

  1. headers(Socket, Request, Headers, HttpLoop, HeaderCount) ->
  2. case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
  3. {ok, http_eoh} ->
  4. inet:setopts(Socket, [{packet, raw}]),
  5. % 将Socket连接、HTTP请求信息(路径、请求方法、HTTP版本)以及请求消息头列表打包成参数化模块(mochiweb_request)的实例对象
  6. Req = mochiweb:new_request({Socket, Request,
  7. lists:reverse(Headers)}),
  8. HttpLoop(Req), % 让用户编写的函数处理HTTP请求
  9. case Req:should_close() of
  10. true ->
  11. gen_tcp:close(Socket),
  12. exit(normal);
  13. false ->
  14. Req:cleanup(),
  15. mochiweb_http:loop(Socket, HttpLoop)
  16. end;
  17. {ok, {http_header, _, Name, _, Value}} ->
  18. headers(Socket, Request, [{Name, Value} | Headers], HttpLoop,
  19. 1 + HeaderCount);
  20. _Other ->
  21. gen_tcp:close(Socket),
  22. exit(normal)
  23. end.

以上处理逻辑都被重新包装到新函数Loop(又一个函数对象),这一包装过程是在mochiweb_http:parse_options函数中进行的。新的Loop函数对象将作为OTP gen_server模块mochiweb_socket_server的运行状态(State)的一部分(这个OTP server模块被mochiweb_http启动,Loop函数对象作为启动参数之一传给mochiweb_socket_server,进入模块的State)

三、接受HTTP请求

作为Mochiweb HTTP Web服务器的核心,mochiweb_socket_server模块负责处理浏览器的HTTP连接(本质上还是socket连接)。

该OTP模块在初始时(init(…)函数)打开trap_exit使服务器进程成为系统进程,并开始在端口监听网络连接,同时,启动(spawn_link)一个HTTP请求接收进程,这个进程执行的是acceptor_loop(…)函数。acceptor_loop接收一个三元tuple作为参数,tuple参数的第一个元素是mochiweb_socket_server服务器进程Pid,第二个是在网络端口进行监听的监听器,第三个就是前面提到的包装好的Loop函数对象,如下:

  1. acceptor_loop({Server, Listen, Loop}) ->
  2. case catch gen_tcp:accept(Listen) of
  3. {ok, Socket} ->
  4. gen_server:cast(Server, {accepted, self()}),
  5. call_loop(Loop, Socket);
  6. {error, closed} ->
  7. exit({error, closed});
  8. Other ->
  9. error_logger:error_report(
  10. [{application, mochiweb},
  11. "Accept failed error",
  12. lists:flatten(io_lib:format("~p", [Other]))]),
  13. exit({error, accept_failed})
  14. end.

当HTTP请求接收进程收到一个浏览器HTTP连接请求后,先以“发送后不管”的方式(即cast)发accepted消息通知mochiweb_socket_server服务器:有HTTP请求来啦,本进程已接受并负责处理。随后该进程就会调用用户定制的函数对象处理请求。进程是在call_loop函数中执行上述的Loop函数对象

call_loop(Loop, Socket) ->
    Loop(Socket).  

这个在服务器初始阶段启动的HTTP请求接收进程的Pid被作为mochiweb_socket_server状态的一部分保持在State记录的acceptor字段。mochiweb_socket_server服务器进程的状态还记录了Mochiweb服务器当前能处理的最大连接数(max),每当收到HTTP请求接收进程发过来的一个accepted消息,就减少一个最大连接数,同时,除非当前最大连接数为零,再生成一个新的HTTP请求接收进程,并将mochiweb_socket_server服务器进程的运行状态(State)的acceptor字段替换成这个新的HTTP请求接收进程Pid。这就是Mochiweb接受HTTP请求的策略,有点像自动步枪,尽管枪膛里只有一发子弹,但每射出一发子弹,火药爆炸带来的一部分气体压力自动的将新的子弹压入枪膛,于是就可以连续不断的射击了。

总结

服务器端的业务逻辑在一个函数中实现,然后这个函数像数据对象那样嵌入到Mochiweb中,这是典型的函数式程序设计。这个函数通过mochiweb_request实例对象获取请求的相关信息,并将响应通过该对象发送给客户端浏览器。

每个HTTP请求由一个Erlang进程负责处理,充分发挥了Erlang多线程的优势。一个连接对应一个Erlang进程,比较适合comet这样的应用,并发请求的处理能力与Erlang线程数量正相关。相关测试见A Million-user Comet Application with Mochiweb 。

Mochiweb的实现大量使用了参数化模块的特性,包括核心的代表HTTP请求的mochiweb_request实例对象,某种程度上使Mochiweb有了基于对象(Object-based)的特点。不过参数化模块还是“undocumented and unsupported”,可能在未来版本的Erlang中有一定不兼容风险,但是如果Mochiweb用的人足够多,应用足够广,我想将来的Erlang说不定反受Mochiweb应用的影响,官方确定参数化模块这一特性呢。

mochiweb这类的轻量级Web服务器小巧灵活,它的Web应用不能像JSP、ASP那样方便的写服务器端代码,也没有很多重型Web服务器常见的功能,但胜在易于理解,又足够快。比较适合纯动态内容生成的Web应用,如comet。可以作为现有Web服务器的补充。Facebook就用它做了Web在线聊天

Mochiweb的设计分析的更多相关文章

  1. C#小程序呢飞行棋设计分析

    C#小程序飞行棋,程序效果图 1.设计分析 这个程序界面大致分为四部分: ① 最上面游戏名字界面 ②信息提示区 ③游戏界面区 ④游戏操作提示区 2.分区设计实现 一.游戏界面显示区,由于只需要显示出图 ...

  2. Web API应用架构设计分析(2)

    在上篇随笔<Web API应用架构设计分析(1)>,我对Web API的各种应用架构进行了概括性的分析和设计,Web API 是一种应用接口框架,它能够构建HTTP服务以支撑更广泛的客户端 ...

  3. 脊柱外科病人资料管理系统的界面设计分析(2)--JOA评分记录的实现

    在上篇随笔<脊柱外科病人资料管理系统的界面设计分析>中介绍了一些常用的界面设计方面的内容,本篇继续上一篇,介绍脊柱外科病人管理系统的JOA评分记录模块的界面设计以及实现方面的内容. JOA ...

  4. Magento架构分析,Magento MVC 设计分析

    Magento架构分析,Magento MVC 设计分析 分类:Magento 标签:Magento MVC.Magento架构 669人浏览 Magento 采用类似 JAVA的架构,其扩展与稳定性 ...

  5. XCOM2中敌对生物设计分析(ADVENT篇)

    最近,在制作游戏Demo--DroneAssmble的过程中,对于敌对生物的设计,参考了幽浮系列的相关设定,因此着手对幽浮2中的主要敌人进行分析. 我们知道, XCOM2中的敌对生物主要由" ...

  6. XCOM2中敌对生物设计分析(Aliens篇)

    Aliens Aliens作为游戏设定中入侵的外星人,有各式外貌及奇特的战斗方式,掌握一些高能科技或利用精神力量进行攻击 Sectoid 使用灵能战斗的外星人,并无高级版本,初级便会使用精神控制,生命 ...

  7. Winform开发框架中工作流模块的表设计分析

    在较早博客随笔里面写过文章<Winform开发框架之简易工作流设计>之后,很久没有对工作流部分进行详细的介绍了,本篇继续这个主题,详细介绍其中的设计.实现及效果给大家,这个工作流在好几年前 ...

  8. 转载:MochiWeb一些资料的链接

    转自:http://veniceweb.googlecode.com/svn/trunk/public/daily_tech_doc/mochiweb_20091030.txt MochiWeb项目主 ...

  9. 基于角色权限管理:rbac设计分析以及具体细节

    权限管理---设计分析以及具体细节 说起权限我们大家都知道,不一样的角色会有不一样的权限. 比如就像学生管理系统一样,管理员,老师,学生之间的权限都是不一样的,那么展示的页面也是不一样的. 所以,我们 ...

随机推荐

  1. 学习笔记:Vue——处理边界情况

    访问元素&组件 01.访问根实例 $root // Vue 根实例 new Vue({ data: { foo: 1 }, computed: { bar: function () { /* ...

  2. Python 极简教程(十)集合 set

    什么是集合? 集合(set)是一种可变,无序和不重复的序列. 集合是python的序列之一,集合没有列表(list).元组(tuple)和字典(ditc)常见.但是有时候也有奇效. 我们先来看个集合的 ...

  3. crm2013 查看下拉框的选项

    在CRM2011中,我们非常easy查看下拉框的选择.打开页面,按F12.把光标对准目标,就会显示出详细的选项,如图:' watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi ...

  4. Altium Designer如何删除以布的线

  5. ThreadPoolExecutor – Java Thread Pool Example(如何使用Executor框架创建一个线程池)

    Java thread pool manages the pool of worker threads, it contains a queue that keeps tasks waiting to ...

  6. TensorFlow 学习(十五)—— tensorflow.python.platform

    tensorflow.python.platform 下的常用工具类和工具函数:tensorflow/tensorflow/python/platform at master · tensorflow ...

  7. UVA 10047 - The Monocycle BFS

    http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&p ...

  8. UVA 11090 - Going in Cycle!! SPFA

    http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&p ...

  9. 8.6 Android灯光系统_源码分析_背光灯

    Change system screen brightness, using android.provider.Settings.System.SCREEN_BRIGHTNESSandroid-er. ...

  10. ios移动旋转缩放动画

    //移动旋转动画效果 CATransform3D rotate = CATransform3DMakeRotation(70.0 * M_PI / 180.0, 0.0, 0.0, 1.0); CAT ...