大家好,这边给大家推荐一本马上要上市的书《Erlang/OTP并发编程实战》,想要学习Erlang的朋友不要错过,这本书的作者是Martin Logan, Eric Merritt, Richard Carlsson,译者是百度的连城,具体可以看下这里:
好了,广告发完了,回到今天的正题,继续跟大家分享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_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 = keepalivecase {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.
我们在 提到过这个方法,只不过但是匹配的分支是第一个分支,而这次匹配的分支是第三个分支:
{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.
我们看下这里,回忆下这个函数,这里再次从客户端Socket读取消息,5000毫秒的超时,如果超过,未读取到消息,则会调用:
{error, _Reason} -> terminate(State)
这个函数之前也讲过, 关闭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,超时。
好了,今天就到这吧,谢谢大家的支持,大家好梦。