代码改变世界

Cowboy 源码分析(二十八)

2012-07-09 08:03  rhinovirus  阅读(1631)  评论(0编辑  收藏  举报

  大家好,上一篇我们总结了cowboy_examples这个例子启动时,进程的启动总结,这一篇,我们看下cowboy如何响应来自浏览器的请求。

  首先我们看下,在上一篇cowboy_acceptors_sup启动的100个cowboy_acceptor工作进程,而这个工作进程在初始化时,同时等待来自浏览器的Socket连接,代码如下:

-spec start_link(inet:socket(), module(), module(), any(),
    pid(), pid()) -> {ok, pid()}.
start_link(LSocket, Transport, Protocol, Opts,
        ListenerPid, ReqsSup) ->
    Pid = spawn_link(?MODULE, acceptor,
        [LSocket, Transport, Protocol, Opts, 1, ListenerPid, ReqsSup]),
    {ok, Pid}.

%% Internal.

-spec acceptor(inet:socket(), module(), module(), any(),
    non_neg_integer(), pid(), pid()) -> no_return().
acceptor(LSocket, Transport, Protocol, Opts, OptsVsn, ListenerPid, ReqsSup) ->
    Res = case Transport:accept(LSocket, 2000) of
        {ok, CSocket} ->
            {ok, Pid} = supervisor:start_child(ReqsSup,
                [ListenerPid, CSocket, Transport, Protocol, Opts]),
            Transport:controlling_process(CSocket, Pid),
            cowboy_listener:add_connection(ListenerPid,
                default, Pid, OptsVsn);
        {error, timeout} ->
            cowboy_listener:check_upgrades(ListenerPid, OptsVsn);
        {error, _Reason} ->
            %% @todo Probably do something here. If the socket was closed,
            %%       we may want to try and listen again on the port?
            ok
    end,
    case Res of
        ok ->
            ?MODULE:acceptor(LSocket, Transport, Protocol,
                Opts, OptsVsn, ListenerPid, ReqsSup);
        {upgrade, Opts2, OptsVsn2} ->
            ?MODULE:acceptor(LSocket, Transport, Protocol,
                Opts2, OptsVsn2, ListenerPid, ReqsSup)
    end.

  这里调用cowboy_tcp_transport:accept/2等待一个Socket连接上,如果有Socket连接上,就会添加一个子进程到ReqsSup这棵树下,代码如下:{ok, Pid} = supervisor:start_child(ReqsSup, [ListenerPid, CSocket, Transport, Protocol, Opts]),函数如下:

-spec start_request(pid(), inet:socket(), module(), module(), any())
    -> {ok, pid()}.
start_request(ListenerPid, Socket, Transport, Protocol, Opts) ->
    Protocol:start_link(ListenerPid, Socket, Transport, Opts).

  这个函数调用 cowboy_http_protocol:start_link/4 函数代码如下:

%% @doc Start an HTTP protocol process.
-spec start_link(pid(), inet:socket(), module(), any()) -> {ok, pid()}.
start_link(ListenerPid, Socket, Transport, Opts) ->
    Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport, Opts]),
    {ok, Pid}.

  如果没有获取Sockt连接超时2秒,则调用cowboy_listener:check_upgrades(ListenerPid, OptsVsn),然后继续递归调用?MODULE:acceptor(LSocket, Transport, Protocol, Opts, OptsVsn, ListenerPid, ReqsSup),这里跟我之前理解的是有偏差的,之前我没注意到这个超时处理和递归,我一直认为代码会一直停留在 Res = case Transport:accept(LSocket, 2000) of 这一行,知道有Socket连接上,完全没注意到超时处理,这里提醒下大家吧,我们可以注意下下图,这个模块一直是running,这里我把之前的100个子进程,修改成1个了,所以图上就1个running的cowboy_acceptor模块。

  

  然后我们看下这行代码cowboy_tcp_transport:controlling_process/2,代码如下:

%% @doc Assign a new controlling process <em>Pid</em> to <em>Socket</em>.
%% @see gen_tcp:controlling_process/2
-spec controlling_process(inet:socket(), pid())
    -> ok | {error, closed | not_owner | atom()}.
controlling_process(Socket, Pid) ->
    gen_tcp:controlling_process(Socket, Pid).

  这个函数的作用是把一个套接字的控制进程改为新的控制进程Pid,之前详细讲过,这里不重复。

  最后调用函数 cowboy_listener:add_connection/4,代码如下:

-spec add_connection(pid(), atom(), pid(), non_neg_integer())
    -> ok | {upgrade, any(), non_neg_integer()}.
add_connection(ServerPid, Pool, ConnPid, OptsVsn) ->
    gen_server:call(ServerPid, {add_connection, Pool, ConnPid, OptsVsn},
        infinity).

  添加连接到cowboy_listener,具体的逻辑我们就不分析了,之前说过了。

  还有个重点就是,关于自定义handler的调用,主要的代码都在 cowboy_http_protocol 模块中,一共有4个函数:

  调用自定义handler:init/3函数:

-spec handler_init(#http_req{}, #state{}) -> ok.
handler_init(Req, State=#state{transport=Transport,
        handler={Handler, Opts}}) ->
    try Handler:init({Transport:name(), http}, Req, Opts) of
        {ok, Req2, HandlerState} ->
            handler_handle(HandlerState, Req2, State);
        {loop, Req2, HandlerState} ->
            handler_before_loop(HandlerState, Req2, State);
        {loop, Req2, HandlerState, hibernate} ->
            handler_before_loop(HandlerState, Req2,
                State#state{hibernate=true});
        {loop, Req2, HandlerState, Timeout} ->
            handler_before_loop(HandlerState, Req2,
                State#state{loop_timeout=Timeout});
        {loop, Req2, HandlerState, Timeout, hibernate} ->
            handler_before_loop(HandlerState, Req2,
                State#state{hibernate=true, loop_timeout=Timeout});
        {shutdown, Req2, HandlerState} ->
            handler_terminate(HandlerState, Req2, State);
        %% @todo {upgrade, transport, Module}
        {upgrade, protocol, Module} ->
            upgrade_protocol(Req, State, Module)
    catch Class:Reason ->
        error_terminate(500, State),
        PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
        error_logger:error_msg(
            "** Handler ~p terminating in init/3~n"
            "   for the reason ~p:~p~n"
            "** Options were ~p~n"
            "** Request was ~p~n** Stacktrace: ~p~n~n",
            [Handler, Class, Reason, Opts, PLReq, erlang:get_stacktrace()])
    end.

  调用自定义handler:handle/2函数:

-spec handler_handle(any(), #http_req{}, #state{}) -> ok.
handler_handle(HandlerState, Req, State=#state{handler={Handler, Opts}}) ->
    try Handler:handle(Req, HandlerState) of
        {ok, Req2, HandlerState2} ->
            terminate_request(HandlerState2, Req2, State)
    catch Class:Reason ->
        ......end.

  调用自定义handler:info/3函数:

-spec handler_call(any(), #http_req{}, #state{}, any()) -> ok.
handler_call(HandlerState, Req, State=#state{handler={Handler, Opts}},
        Message) ->
    try Handler:info(Message, Req, HandlerState) of
        {ok, Req2, HandlerState2} ->
            terminate_request(HandlerState2, Req2, State);
        {loop, Req2, HandlerState2} ->
            handler_before_loop(HandlerState2, Req2, State);
        {loop, Req2, HandlerState2, hibernate} ->
            handler_before_loop(HandlerState2, Req2,
                State#state{hibernate=true})
    catch Class:Reason ->
        ......end.

  调用自定好handler:terminate/2函数:

-spec handler_terminate(any(), #http_req{}, #state{}) -> ok.
handler_terminate(HandlerState, Req, #state{handler={Handler, Opts}}) ->
    try
        Handler:terminate(Req#http_req{resp_state=locked}, HandlerState)
    catch Class:Reason ->
        ......end.

  关于分解请求包和构建响应包,这里不打算重复讲了,因为涉及的函数比较多,也没什么特别的地方。

  好了,这个系列终于结束了,花了很多时间在cowboy项目上,也学到了很多,之后相信还会有机会再跟大家分享cowboy的源码,因为我下一个打算看的项目是mochiweb,也是基于http协议的erlang应用,可能到时候会对比下这两个项目吧。

  这里,也希望博客园的朋友,尝试下Erlang这门语言吧,多会一门语言对你的编程人生还是有帮助的,至少你可以了解另一个编程世界的不同思想。

  谢谢大家的耐心收看,也欢迎大家和我交流,可以关注我的微博,也可以直接给我留言。