您的位置:首页 > 其它

Cowboy 源码分析(二十四)

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

  cowboy_http_protocol:next_request[b]/3[/b]函数代码如下:

-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[b]/3 [/b]函数,我们来看下这一行:{BodyRes, Buffer} = ensure_body_processed(Req), 这里调用 cowboy_http_protocol:[b]ensure_body_processed/1[/b] 函数:

-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 函数,继续往下看了。

  


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

  最后,谢谢大家支持。

  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: