代码改变世界

Cowboy 源码分析(二十六)

2012-07-04 00:30  rhinovirus  阅读(1465)  评论(0编辑  收藏  举报

  大家好,这边给大家推荐一本马上要上市的书《Erlang/OTP并发编程实战》,想要学习Erlang的朋友不要错过,这本书的作者是Martin Logan, Eric Merritt, Richard Carlsson,译者是百度的连城,具体可以看下这里:http://www.ituring.com.cn/book/828

  好了,广告发完了,回到今天的正题,继续跟大家分享Cowboy源码。上一篇,讲完了 cowboy_http_req:body_length/1 函数,今天继续往下看,如图:

  

  这里返回的 Length = 0,所以这里返回 {done, Req3#http_req{body_state=done}};

  如果返回的不是0,会递归调用 cowboy_http_req:stream_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 返回值,这里返回 {ok, Req2};

  这样,我们又回到cowboy_http_protocol:ensure_body_processed/1 函数:

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;

  这里 Req2#http_req.buffer = <<>>,这里返回 {ok, <<>>};

  继续,回到 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.

  我们看下这一行,这里我们稍微修改,增加一些打印日志,来看函数的执行过程:

    %% Flush the resp_sent message before moving on.
    receive {cowboy_http_req, resp_sent} -> 
        io:format("Flush the resp_sent message before moving on.~n"),
        ok 
    after 0 -> 
        io:format("after 0 -> ok end.~n"),
        ok 
    end,

  不知道大家对 超时时间为0的receive 有没印象,我在文章 Cowboy 源码分析(十八) 提到过,这里我测试的结果,表示我之前的理解是有错误的,我简单描述下,之前我的理解是,如果邮箱中存在 {cowboy_http_req, resp_sent},则会打印 “Flush the resp_sent message before moving on. ”,紧接着立即触发超时,打印 ”after 0 -> ok end.“, 然而这里打印的日志证明我理解错了,正确的测试结果,如果邮箱中存在 {cowboy_http_req, resp_sent},则打印 ”Flush the resp_sent message before moving on.“,如果邮箱中不存在该消息,则立即触发超时,打印 ”after 0 -> ok end.~n“。

  这边感谢网友的帮助 鲁雪林,他的测试例子如下:

-module(main).

-compile(export_all).

recv()->
    receive
        a ->
        io:format("a.~n")
    after 0 ->
        io:format("b.~n")
    end. 

  测试结果如下:

  

  希望能帮助大家理解 after 0 的情况。

  好了,我们继续往下看:

  < HandlerRes = ok
  < BodyRes = ok
  < RespRes = ok
  < Conn = keepalive

    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.

  从上面的结果,能够知道匹配第一个分支,也就是调用 cowboy_http_protocol:parse_request/1 函数,这里有个疑问,为什么不用 cowboy_http_protocol 而用?MODULE 呢?如果有朋友知道,麻烦告知,不过不影响,我们继续:

%% @private
-spec parse_request(#state{}) -> ok.
%% We limit the length of the Request-line to MaxLength to avoid endlessly
%% reading from the socket and eventually crashing.
parse_request(State=#state{buffer=Buffer, max_line_length=MaxLength}) ->
    case erlang:decode_packet(http_bin, Buffer, []) of
        {ok, Request, Rest} -> request(Request, State#state{buffer=Rest});
        {more, _Length} when byte_size(Buffer) > MaxLength ->
            error_terminate(413, State);
        {more, _Length} -> wait_request(State);
        {error, _Reason} -> error_terminate(400, State)
    end.

  我们在 Cowboy 源码分析(九) 提到过这个方法,只不过但是匹配的分支是第一个分支,而这次匹配的分支是第三个分支:

{more, _Length} -> wait_request(State);

  这里就一行代码,调用cowboy_http_protocol:wait_request/1 函数:

-spec wait_request(#state{}) -> ok.
wait_request(State=#state{socket=Socket, transport=Transport,
        timeout=T, buffer=Buffer}) ->
    case Transport:recv(Socket, 0, T) of
        {ok, Data} -> 
        io:format("Data = ~p~n", [Data]),
        parse_request(State#state{
            buffer= << Buffer/binary, Data/binary >>});
        {error, _Reason} -> terminate(State)
    end.

  我们看下这里Cowboy 源码分析(九),回忆下这个函数,这里再次从客户端Socket读取消息,5000毫秒的超时,如果超过,未读取到消息,则会调用:

{error, _Reason} -> terminate(State)

  这个函数之前也讲过,Cowboy 源码分析(九) 关闭Socket,这里我比较好奇,关闭原因,所以我增加打印日志,如下:

    case Transport:recv(Socket, 0, T) of
        {ok, Data} -> 
        io:format("Data = ~p~n", [Data]),
        parse_request(State#state{
            buffer= << Buffer/binary, Data/binary >>});
        {error, _Reason} -> 
            io:format("_Reason: ~p~n", [_Reason]),
            terminate(State)
    end.

  运行,得到如下结果:

  

  如图,原因是 timeout,超时。

  好了,今天就到这吧,谢谢大家的支持,大家好梦。