Mochiweb的设计分析续

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

  1. headers(SocketRequestHeadersHttpLoopHeaderCount) -> 
  2.     case gen_tcp:recv(Socket0, ?IDLE_TIMEOUT) of 
  3.         {okhttp_eoh} -> 
  4.             inet:setopts(Socket[{packetraw}])
  5.             % 将Socket连接、HTTP请求信息(路径、请求方法、HTTP版本)以及请求消息头列表打包成参数化模块(mochiweb_request)的实例对象 
  6.             Req = mochiweb:new_request({SocketRequest
  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(SocketHttpLoop) 
  16.             end
  17.         {ok, {http_header_Name_Value}} -> 
  18.             headers(SocketRequest[{NameValue} | 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({ServerListenLoop}) -> 
  2.     case catch gen_tcp:accept(Listen) of 
  3.         {okSocket} -> 
  4.             gen_server:cast(Server, {acceptedself()})
  5.             call_loop(LoopSocket)
  6.         {errorclosed} -> 
  7.             exit({errorclosed})
  8.         Other -> 
  9.             error_logger:error_report( 
  10.               [{applicationmochiweb}, 
  11.                "Accept failed error"
  12.                lists:flatten(io_lib:format("~p"[Other]))])
  13.             exit({erroraccept_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在线聊天

 

你可能感兴趣的:(应用服务器,erlang,socket,网络应用,Comet)