代码改变世界

mochiweb 源码阅读(十三)

2012-07-31 00:59  rhinovirus  阅读(1819)  评论(4编辑  收藏  举报

  大家好,这一篇我们来看下上一篇没有讲完的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).

  谢谢大家的耐心阅读,晚安。