代码改变世界

Cowboy 源码分析(二十四)

2012-06-29 08:40  rhinovirus  阅读(1468)  评论(2编辑  收藏  举报

  大家好,调整下作息时间,以后尽量不熬夜写文章了,改成早上早起写。试试这样的作息习惯吧,不是有句话说吗,早睡早起精神好,其实主要是熬夜确实对身体不太好。好了,回归正题,上一篇,我们分析到 cowboy_http_protocol:terminate_request/3 函数第一行,今天我们来看下一行,也就是:next_request(Req, State, HandlerRes).

  cowboy_http_protocol:next_request/3函数代码如下:

-spec next_request(#http_req{}, #state{}, any()) -> ok.
next_request(Req=#http_req{connection=Conn}, State=#state{
        req_keepalive=Keepalive}, HandlerRes) ->
    RespRes = ensure_response(Req),
    {BodyRes, Buffer} = ensure_body_processed(Req),
    %% Flush the resp_sent message before moving on.
    receive {cowboy_http_req, resp_sent} -> ok after 0 -> ok end,
    case {HandlerRes, BodyRes, RespRes, Conn} of
        {ok, ok, ok, keepalive} ->
            ?MODULE:parse_request(State#state{
                buffer=Buffer, req_empty_lines=0,
                req_keepalive=Keepalive + 1});
        _Closed ->
            terminate(State)
    end. 

  这个函数接受三个参数,其中前两个,我们比较熟悉了,最后一个是 HandlerRes = handler_terminate(HandlerState, Req, State), ,第一行代码的返回值,如果正常返回就是 ok,具体可以看上一篇文章有关这个函数的详细分析。

  我们来看下具体逻辑:

  RespRes = ensure_response(Req), 这里调用cowboy_http_protocol:ensure_response/1 函数,该函数完整代码如下:

-spec ensure_response(#http_req{}) -> ok.
%% The handler has already fully replied to the client.
ensure_response(#http_req{resp_state=done}) ->
    ok;
%% No response has been sent but everything apparently went fine.
%% Reply with 204 No Content to indicate this.
ensure_response(Req=#http_req{resp_state=waiting}) ->
    _ = cowboy_http_req:reply(204, [], [], Req),
    ok;
%% Terminate the chunked body for HTTP/1.1 only.
ensure_response(#http_req{method='HEAD', resp_state=chunks}) ->
    ok;
ensure_response(#http_req{version={1, 0}, resp_state=chunks}) ->
    ok;
ensure_response(#http_req{socket=Socket, transport=Transport,
        resp_state=chunks}) ->
    Transport:send(Socket, <<"0\r\n\r\n">>),
    ok.

  这个函数会根据参数的不同,逻辑稍有不同,最后返回 ok,这里会调用第一个分支:

%% The handler has already fully replied to the client.
ensure_response(#http_req{resp_state=done}) ->
    ok;

  回到 cowboy_http_protocol:next_request/3 函数,我们来看下这一行:{BodyRes, Buffer} = ensure_body_processed(Req), 这里调用 cowboy_http_protocol:ensure_body_processed/1 函数:

-spec ensure_body_processed(#http_req{}) -> {ok | close, binary()}.
ensure_body_processed(#http_req{body_state=done, buffer=Buffer}) ->
    {ok, Buffer};
ensure_body_processed(Req=#http_req{body_state=waiting}) ->
    case cowboy_http_req:skip_body(Req) of
        {ok, Req2} -> {ok, Req2#http_req.buffer};
        {error, _Reason} -> {close, <<>>}
    end;
ensure_body_processed(Req=#http_req{body_state={multipart, _, _}}) ->
    {ok, Req2} = cowboy_http_req:multipart_skip(Req),
    ensure_body_processed(Req2).

  这里从Debugger,可以知道 body_state=waiting,所以调用下面第二个分支:

ensure_body_processed(Req=#http_req{body_state=waiting}) ->
    case cowboy_http_req:skip_body(Req) of
        {ok, Req2} -> {ok, Req2#http_req.buffer};
        {error, _Reason} -> {close, <<>>}
    end;

  这个函数逻辑也比较简单,调用 cowboy_http_req:skip_body/1 函数,然后根据不同返回值,返回不同的状态。这里我们看下cowboy_http_req:skip_body/1 函数:

-spec skip_body(#http_req{}) -> {ok, #http_req{}} | {error, atom()}.
skip_body(Req) ->
    case stream_body(Req) of
        {ok, _, Req2} -> skip_body(Req2);
        {done, Req2} -> {ok, Req2};
        {error, Reason} -> {error, Reason}
    end.

  这里主要看下 cowboy_http_req:stream_body/1 函数:

%% @doc Stream the request's body.
%%
%% This is the most low level function to read the request body.
%%
%% In most cases, if they weren't defined before using stream_body/4,
%% this function will guess which transfer and content encodings were
%% used for building the request body, and configure the decoding
%% functions that will be used when streaming.
%%
%% It then starts streaming the body, returning {ok, Data, Req}
%% for each streamed part, and {done, Req} when it's finished streaming.
-spec stream_body(#http_req{}) -> {ok, binary(), #http_req{}}
    | {done, #http_req{}} | {error, atom()}.
stream_body(Req=#http_req{body_state=waiting}) ->
    case parse_header('Transfer-Encoding', Req) of
        {[<<"chunked">>], Req2} ->
            stream_body(Req2#http_req{body_state=
                {stream, fun cowboy_http:te_chunked/2, {0, 0},
                 fun cowboy_http:ce_identity/1}});
        {[<<"identity">>], Req2} ->
            {Length, Req3} = body_length(Req2),
            case Length of
                0 ->
                    {done, Req3#http_req{body_state=done}};
                Length ->
                    stream_body(Req3#http_req{body_state=
                        {stream, fun cowboy_http:te_identity/2, {0, Length},
                         fun cowboy_http:ce_identity/1}})
            end
    end;
stream_body(Req=#http_req{buffer=Buffer, body_state={stream, _, _, _}})
        when Buffer =/= <<>> ->
    transfer_decode(Buffer, Req#http_req{buffer= <<>>});
stream_body(Req=#http_req{body_state={stream, _, _, _}}) ->
    stream_body_recv(Req);
stream_body(Req=#http_req{body_state=done}) ->
    {done, Req}.

  这里,首先匹配第一个分支,我们看下第一个分支的代码:

  case parse_header('Transfer-Encoding', Req) of

  调用  cowboy_http_req:parse_header/2 函数:

%% @doc Semantically parse headers.
%%
%% When the value isn't found, a proper default value for the type
%% returned is used as a return value.
%% @see parse_header/3
-spec parse_header(cowboy_http:header(), #http_req{})
    -> {any(), #http_req{}} | {error, badarg}.
parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
    case lists:keyfind(Name, 1, PHeaders) of
        false -> parse_header(Name, Req, parse_header_default(Name));
        {Name, Value} -> {Value, Req}
    end.

  调用这个函数时,接受到的参数的值为:

  < PHeaders = [{'Connection',[<<"keep-alive">>]}]
  < Name = 'Transfer-Encoding'

   这个函数,我在Cowboy 源码分析(十三) Cowboy 源码分析(十四) Cowboy 源码分析(十五) 介绍过这个函数。只不过当时参数为:

  < PHeaders = []
  < Name = 'Connection'

  这里我们还是再看下,很明显,这里 lists:keyfind(Name, 1, PHeaders) 为false,得到的肯定是 false,紧接着调用 cowboy_http_req:parse_header/3 这个函数,其中最后一个参数调用的函数代码如下:

%% @doc Default values for semantic header parsing.
-spec parse_header_default(cowboy_http:header()) -> any().
parse_header_default('Connection') -> [];
parse_header_default('Transfer-Encoding') -> [<<"identity">>];
parse_header_default(_Name) -> undefined.

  这里Name = 'Transfer-Encoding'所以这个函数返回:[<<"identity">>];

  再看 cowboy_http_req:parse_header/3 这个函数,函数比较大,我不贴全部了,就贴调用的部分:

parse_header(Name, Req, Default) when Name =:= 'Transfer-Encoding' ->
    parse_header(Name, Req, Default,
        fun (Value) ->
            cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
        end);

  这个分支除了接受到的Name不同,其他跟上次讲过的没什么区别了,不重复造车了。

  但是,我们依然需要关注下返回值,看下面代码:  

%% @todo This doesn't look in the cache.
parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) ->
    case header(Name, Req) of
        {undefined, Req2} ->
            {Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}};
        {Value, Req2} ->
            case Fun(Value) of
                {error, badarg} ->
                    {error, badarg};
                P ->
                    {P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}}
            end
    end.

  如下图:

  

  好了,知道返回值从哪得来的。我们就可以回到 cowboy_http_req:stream_body/1 函数,继续往下看了。

  

  从上图,我们根据返回值,就可以继续往下看了,好了,今天就到这,下一篇继续跟大家分享。

  最后,谢谢大家支持。