Cowboy 源码分析(二十二)

  今天又是早早下班,买了排骨,莲藕,现在正压着排骨莲藕汤,从小就学做饭,发现其实做饭也蛮多乐趣的。等这篇文章发布以后,我应该已经吃完很久了,哈哈。回到主题,上一篇,我们cowboy_http_protocol:dispatch/2 函数,今天我们来看cowboy_http_protocol:handler_init/2 函数

-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 = default_handler
  < Transport = cowboy_tcp_transport
  < Req = {http_req,#Port<0.2731>,cowboy_tcp_transport,keepalive,<0.481.0>,
                  'GET',
                  {1,1},
                  undefined,
                  [<<"localhost">>],
                  undefined,<<"localhost">>,8080,[],undefined,<<"/">>,
                  undefined,<<>>,[],
                  [{'Connection',<<"keep-alive">>},
                   {'Accept-Encoding',<<"gzip, deflate">>},
                   {'Accept-Language',<<"zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3">>},
                   {'Accept',<<"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8">>},
                   {'User-Agent',<<"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0.1">>},
                   {'Host',<<"localhost">>}],
                  [{'Connection',[<<"keep-alive">>]}],
                  undefined,[],waiting,<<>>,waiting,[],<<>>,undefined,
                  {#Fun<cowboy_http.urldecode.2>,crash}}
  < Opts = []

  看下具体逻辑:

  try Handler:init({Transport:name(), http}, Req, Opts) of

  = try default_handler:init({cowboy_tcp_transport:name(), http}, Req, Opts) of

  首先,看下 cowboy_tcp_transport:name/0 函数:

%% @doc Name of this transport API, <em>tcp</em>.
-spec name() -> tcp.
name() -> tcp.

  一个简单的不能再简单的方法,返回原子tcp。

  接着看 default_handler:init/3 函数:

init({_Any, http}, Req, []) ->
    {ok, Req, undefined}.

  这个函数也很简单,只是简单的把参数组成元组返回。

  回到cowboy_http_protocol:handler_init/2 函数

{ok, Req2, HandlerState} ->
    handler_handle(HandlerState, Req2, State);

  我们从default_handler:init/3 函数返回的结果,能知道匹配这个分支,而这里紧接着调用cowboy_http_protocol:handler_handle/3 函数,同时我们注意下,参数的值:

  < HandlerState = undefined
  < Req2 = {http_req,#Port<0.2989>,cowboy_tcp_transport,keepalive,<0.490.0>,
             'GET',
             {1,1},
             undefined,
             [<<"localhost">>],
             undefined,<<"localhost">>,8080,[],undefined,<<"/">>,undefined,
             <<>>,[],
             [{'Connection',<<"keep-alive">>},
              {'Accept-Encoding',<<"gzip, deflate">>},
              {'Accept-Language',<<"zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3">>},
              {'Accept',
                  <<"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8">>},
              {'User-Agent',
                  <<"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0.1">>},
              {'Host',<<"localhost">>}],
             [{'Connection',[<<"keep-alive">>]}],
             undefined,[],waiting,<<>>,waiting,[],<<>>,undefined,
             {#Fun<cowboy_http.urldecode.2>,crash}}
  < State = {state,<0.270.0>,#Port<0.2989>,cowboy_tcp_transport,
                 [{'_',[{[<<"websocket">>],websocket_handler,[]},
                        {[<<"eventsource">>],eventsource_handler,[]},
                        {[<<"eventsource">>,<<"live">>],
                         eventsource_emitter,[]},
                        {'_',default_handler,[]}]}],
                 {default_handler,[]},
                 undefined,undefined,
                 {#Fun<cowboy_http.urldecode.2>,crash},
                 0,5,1,infinity,4096,5000,<<>>,false,infinity,undefined}

  现在我们继续看cowboy_http_protocol:handler_handle/3 函数:

-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 ->
        PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
        error_logger:error_msg(
            "** Handler ~p terminating in handle/2~n"
            "   for the reason ~p:~p~n"
            "** Options were ~p~n** Handler state was ~p~n"
            "** Request was ~p~n** Stacktrace: ~p~n~n",
            [Handler, Class, Reason, Opts,
             HandlerState, PLReq, erlang:get_stacktrace()]),
        handler_terminate(HandlerState, Req, State),
        error_terminate(500, State)
    end.

  和上面差不多:

  try Handler:handle(Req, HandlerState) of

  = try default_handler:handle(Req, HandlerState) of

  调用 default_handler:handle/2 函数:

handle(Req, State) ->
    {ok, Req2} = cowboy_http_req:reply(200, [], <<"Hello world!">>, Req),
    {ok, Req2, State}.

  < Req = {http_req,#Port<0.2990>,cowboy_tcp_transport,keepalive,<0.494.0>,
                  'GET',
                  {1,1},
                  undefined,
                  [<<"localhost">>],
                  undefined,<<"localhost">>,8080,[],undefined,<<"/">>,
                  undefined,<<>>,[],
                  [{'Cache-Control',<<"max-age=0">>},
                   {'Connection',<<"keep-alive">>},
                   {'Accept-Encoding',<<"gzip, deflate">>},
                   {'Accept-Language',<<"zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3">>},
                   {'Accept',<<"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8">>},
                   {'User-Agent',<<"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0.1">>},
                   {'Host',<<"localhost">>}],
                  [{'Connection',[<<"keep-alive">>]}],
                  undefined,[],waiting,<<>>,waiting,[],<<>>,undefined,
                  {#Fun<cowboy_http.urldecode.2>,crash}}
  < HandlerState = undefined

  注意,这里的HandlerStatedefault_handler:init/3 函数返回的状态,继续看:

  {ok, Req2} = cowboy_http_req:reply(200, [], <<"Hello world!">>, Req),

  这里调用 cowboy_http_req:reply/4 函数:

%% @doc Send a reply to the client.
-spec reply(cowboy_http:status(), cowboy_http:headers(), iodata(), #http_req{})
    -> {ok, #http_req{}}.
reply(Status, Headers, Body, Req=#http_req{socket=Socket, transport=Transport,
        version=Version, connection=Connection,
        method=Method, resp_state=waiting, resp_headers=RespHeaders}) ->
    RespConn = response_connection(Headers, Connection),
    ContentLen = case Body of {CL, _} -> CL; _ -> iolist_size(Body) end,
    HTTP11Headers = case Version of
        {1, 1} -> [{<<"Connection">>, atom_to_connection(Connection)}];
        _ -> []
    end,
    {ReplyType, Req2} = response(Status, Headers, RespHeaders,  [
        {<<"Content-Length">>, integer_to_list(ContentLen)},
        {<<"Date">>, cowboy_clock:rfc1123()},
        {<<"Server">>, <<"Cowboy">>}
    |HTTP11Headers], Req),
    if    Method =:= 'HEAD' -> ok;
        ReplyType =:= hook -> ok; %% Hook replied for us, stop there.
        true ->
            case Body of
                {_, StreamFun} -> StreamFun();
                _ -> Transport:send(Socket, Body)
            end
    end,
    {ok, Req2#http_req{connection=RespConn, resp_state=done,
        resp_headers=[], resp_body= <<>>}}.

  不知道大家对这个函数,还有没印象,我在之前的Cowboy 源码分析(十八) Cowboy 源码分析(十九)详细讲过,这边不重复讲,大家翻看之前的文章,回忆下,这里跟之前直接返回HTTP状态码不同的是,这里是正常返回,Status = 200,Headers = [], Body = <<"Hello world!">>。

  该函数,最后返回新的修改后的#http_req记录,紧接着 default_handler:handle/2 函数,把结果重新组合成新的元组返回。

  我们继续往下看:

try Handler:handle(Req, HandlerState) of
        {ok, Req2, HandlerState2} ->
            terminate_request(HandlerState2, Req2, State)

  当调用 default_handler:handle/2 函数返回结果后,会调用cowboy_http_protocol:terminate_request/3 函数:

-spec terminate_request(any(), #http_req{}, #state{}) -> ok.
terminate_request(HandlerState, Req, State) ->
    HandlerRes = handler_terminate(HandlerState, Req, State),
    next_request(Req, State, HandlerRes).

  好了,今天就到这吧,下一篇,我们继续从这个函数往下看吧。

  最后,谢谢大家的支持,虽然看的人不多,但是我会继续坚持写下去的,大家好梦。

 

你可能感兴趣的:(源码分析)