大家好,这一篇我们来看下上一篇没有讲完的mochiweb_acceptor:init/3函数中,关于mochiweb_socket:accept/1函数返回值的处理:
init(Server, Listen, Loop) -> T1 = now(), case catch mochiweb_socket:accept(Listen) of {ok, Socket} -> gen_server:cast(Server, {accepted, self(), timer:now_diff(now(), T1)}), call_loop(Loop, Socket); {error, closed} -> exit(normal); {error, timeout} -> init(Server, Listen, Loop); {error, esslaccept} -> exit(normal); Other -> error_logger:error_report( [{application, mochiweb}, "Accept failed error", lists:flatten(io_lib:format("~p", [Other]))]), exit({error, accept_failed}) end.
如果调用返回:{error, closed},则表示Listen已经关闭,调用exit/1终止当前进程,原因为normal。
如果调用返回:{error, timeout},则表示指定时间内并没有客户端连接到服务器,但是这里从之前讲过的mochiweb_socket:accept/1函数来看,只有非SSL协议,才有添加超时处理,而SSL协议会阻塞在ssl:transport_accept/1这里;回到处理超时的分支,这里递归调用mochiweb_acceptor:init/3函数。
注意:这里我并不是很明白作者为什么只对非SSL协议添加超时处理。
如果调用返回:{error, esslaccept},则表示Server SSL handshake procedure between client and server failed.翻译:客户端和服务器之间的SSL握手过程失败。这个是SSL协议版本的特有处理,调用exit/1终止当前进程,原因为normal。
其他错误Other处理:
Other -> error_logger:error_report( [{application, mochiweb}, "Accept failed error", lists:flatten(io_lib:format("~p", [Other]))]), exit({error, accept_failed})
调用error_logger打印log信息(关于这部分内容,可以参阅《Erlang/OTP并发编程实战》第7章:Erlang/OTP中的日志与事件处理的相关内容),然后调用exit/1终止当前进程,原因为{error, accept_failed}。
最后调用正常,返回:{ok, Socket}时,首先向Server发送一条异步消息,内容为:
{accepted, self(), timer:now_diff(now(), T1)}
这里我们先看下系统函数:timer:now_diff/2,erlang doc 地址:http://www.erlang.org/doc/man/timer.html#now_diff-2,如下图:
大致翻译:当T1和T2为从erlang:now/0返回的时间戳元组,计算时间差Tdiff = T2 - T1为微秒。
注意这里的erlang:timestamp(),为erlang:now/0,如下图:
回到上面的消息内容:包括三个元素的元组,第一项为原子accepted;第二项为:self(),获取当前进程标识;最后一项,我也不是很理解,感觉作者本意是想获取,进程从调用init/3初始化到客户端连接上的时间差,但是由于这里mochiweb_socket:accept/1函数处理非SSL协议时,使用超时为2000毫秒,所以会触发超时,返回:{error, timeout},进而递归调用init/3方法,那么T1就会被重置,所计算的时间就不准确了。所以注意:这里我并不是很理解作者的意图。
这里来回忆下,Server的值是在什么时候传递进来的,如下面代码:
new_acceptor_pool(Listen, State=#mochiweb_socket_server{acceptor_pool=Pool, acceptor_pool_size=Size, loop=Loop}) -> F = fun (_, S) -> Pid = mochiweb_acceptor:start_link(self(), Listen, Loop), sets:add_element(Pid, S) end, Pool1 = lists:foldl(F, Pool, lists:seq(1, Size)), State#mochiweb_socket_server{acceptor_pool=Pool1}.
大家翻看之前的几篇文章,回忆下这个函数,我想你就能明白了。
那么Server中,关于{accepted, self(), timer:now_diff(now(), T1)}这条消息的处理代码如下:
handle_cast({accepted, Pid, Timing}, State=#mochiweb_socket_server{active_sockets=ActiveSockets}) -> State1 = State#mochiweb_socket_server{active_sockets=1 + ActiveSockets}, case State#mochiweb_socket_server.profile_fun of undefined -> undefined; F when is_function(F) -> catch F([{timing, Timing} | state_to_proplist(State1)]) end, {noreply, recycle_acceptor(Pid, State1)};
这里我们先跳过,继续往下看:call_loop(Loop, Socket),该函数完整代码如下:
call_loop({M, F}, Socket) -> M:F(Socket); call_loop({M, F, [A1]}, Socket) -> M:F(Socket, A1); call_loop({M, F, A}, Socket) -> erlang:apply(M, F, [Socket | A]); call_loop(Loop, Socket) -> Loop(Socket).
这四个分支,根据第一个参数的不同匹配进行不同处理;而我们来回忆下Loop这个参数,它的值应该是什么呢?
首先是mochiweb_example_web:start/1定义Loop,完整代码如下:
start(Options) -> {DocRoot, Options1} = get_option(docroot, Options), Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end, mochiweb_http:start([{name, ?MODULE}, {loop, Loop} | Options1]).
这里mochiweb_example_web:loop/2函数完整代码如下:
loop(Req, DocRoot) -> "/" ++ Path = Req:get(path), try case Req:get(method) of Method when Method =:= 'GET'; Method =:= 'HEAD' -> case Path of _ -> Req:serve_file(Path, DocRoot) end; 'POST' -> case Path of _ -> Req:not_found() end; _ -> Req:respond({501, [], []}) end catch Type:What -> Report = ["web request failed", {path, Path}, {type, Type}, {what, What}, {trace, erlang:get_stacktrace()}], error_logger:error_report(Report), %% NOTE: mustache templates need \ because they are not awesome. Req:respond({500, [{"Content-Type", "text/plain"}], "request failed, sorry\n"}) end.
接着是mochiweb_http:parse_options/1对于loop选项的处理,完整代码如下:
parse_options(Options) -> {loop, HttpLoop} = proplists:lookup(loop, Options), Loop = {?MODULE, loop, [HttpLoop]}, Options1 = [{loop, Loop} | proplists:delete(loop, Options)], mochilists:set_defaults(?DEFAULTS, Options1).
其中loop函数,也就是mochiweb_http:loop/2,完整代码如下:
loop(Socket, Body) -> ok = mochiweb_socket:setopts(Socket, [{packet, http}]), request(Socket, Body).
再接着就是mochiweb_socket_server:parse_options/2关于loop元组的处理,如下:
parse_options([{loop, Loop} | Rest], State) ->
parse_options(Rest, State#mochiweb_socket_server{loop=Loop});
这里仅仅是赋值给#mochiweb_socket_server记录,并没有特殊处理。
好了,现在相信大家对Loop变量的值已经明白了,而从mochiweb_http:parse_options/1函数的这一行:Loop = {?MODULE, loop, [HttpLoop]},能够知道这里匹配的分支应该是:
call_loop({M, F, [A1]}, Socket) ->
M:F(Socket, A1);
其他分支也很简单,差别并不是很大,都是调用对应模块下的对应函数,传递对应的参数。
那么这里,就是调用mochiweb_http:loop/2函数,第一个参数为Socket,第二个则为:
Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end,
其中?MODULE=mochiweb_example_web,注意这里使用匿名函数和闭包。
好了,这一篇就到这里,下一篇,我们将从两处代码继续和大家分享mochiweb源码,一处是mochiweb_socket_server:handle_cast/2:
handle_cast({accepted, Pid, Timing}, State=#mochiweb_socket_server{active_sockets=ActiveSockets}) -> State1 = State#mochiweb_socket_server{active_sockets=1 + ActiveSockets}, case State#mochiweb_socket_server.profile_fun of undefined -> undefined; F when is_function(F) -> catch F([{timing, Timing} | state_to_proplist(State1)]) end, {noreply, recycle_acceptor(Pid, State1)};
另一处则是mochiweb_http:loop/2:
loop(Socket, Body) -> ok = mochiweb_socket:setopts(Socket, [{packet, http}]), request(Socket, Body).
谢谢大家的耐心阅读,晚安。