大家好,今天继续跟大家分享Cowboy,我发现最近有点慢,没有刚开始写的那么凶猛了,自己想了想:一个是时间上没办法保证了,另一个是自己有点散漫。得好好调整下,一定要坚持下去。游戏马上要上大平台了,最近版本多,事多,但这都不是理由,呵呵,时间就像女人的乳沟,挤挤还是有的。所以到家,洗个澡,继续努力。好了,说的有点多了,回到正题。
上一篇,我们在文章最后,留下了几个疑问,今天一并解决。
首先是,当 erlang:decode_packet/3,这个函数返回 {ok, Header, Rest},而由于Header参数的值不同,将匹配 cowboy_http_protocol:header/3,函数的各个分支,我们整理下:
第一个分支:
header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{ transport=Transport, host=undefined}, State) -> RawHost2 = cowboy_bstr:to_lower(RawHost), case catch cowboy_dispatcher:split_host(RawHost2) of {Host, RawHost3, undefined} -> Port = default_port(Transport:name()), parse_header(Req#http_req{ host=Host, raw_host=RawHost3, port=Port, headers=[{'Host', RawHost3}|Req#http_req.headers]}, State); {Host, RawHost3, Port} -> parse_header(Req#http_req{ host=Host, raw_host=RawHost3, port=Port, headers=[{'Host', RawHost3}|Req#http_req.headers]}, State); {'EXIT', _Reason} -> error_terminate(400, State) end;
这个分支,Header参数的值为如下时,会调用这个分支:
< Header = {http_header,14,'Host',undefined,<<"localhost:8080">>}
< Rest = <<"User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n">>
这个分支里的代码,我不再讲了,我在之前的Cowboy 源码分析(十二) 中详细讲解了,大家可以回忆下。
第二个分支:
header({http_header, _I, 'Host', _R, _V}, Req, State) ->
parse_header(Req, State);
这个分支在第一个分支后面,我们看下,其实这个分支,很简单的调用了cowboy_http_protocol:parse_header/2 函数,这个分支,好像没有用到,以后如果用到,我们再看吧。
第三个分支:
header({http_header, _I, 'Connection', _R, Connection}, Req=#http_req{headers=Headers}, State=#state{ req_keepalive=Keepalive, max_keepalive=MaxKeepalive}) when Keepalive < MaxKeepalive -> Req2 = Req#http_req{headers=[{'Connection', Connection}|Headers]}, {ConnTokens, Req3} = cowboy_http_req:parse_header('Connection', Req2), ConnAtom = cowboy_http:connection_to_atom(ConnTokens), parse_header(Req3#http_req{connection=ConnAtom}, State);
同样的,Header参数的值为如下时,会调用这个分支:
< Header = {http_header,2,'Connection',undefined,<<"keep-alive">>}
< Rest = <<"\r\n">>
这个分支里的代码,我们也详细讲过,如果你还记得 Cowboy 源码分析(十三) 这一篇,我们很详细的讲解了这个方法。
第四个分支:
header({http_header, _I, Field, _R, Value}, Req, State) -> Field2 = format_header(Field), parse_header(Req#http_req{headers=[{Field2, Value}|Req#http_req.headers]}, State);
这个分支,Field,并不是常量,而是变量,我们可以发现,当Header参数的值为以下这些时,将调用这个分支:
< Header = {http_header,24,'User-Agent',undefined,
<<"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0">>}
< Rest = <<"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n">>
< Header = {http_header,8,'Accept',undefined,
<<"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8">>}
< Rest = <<"Accept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n">>
< Header = {http_header,11,'Accept-Language',undefined,<<"en-us,en;q=0.5">>}
< Rest = <<"Accept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n">>
< Header = {http_header,10,'Accept-Encoding',undefined,<<"gzip, deflate">>}
< Rest = <<"Connection: keep-alive\r\n\r\n">>
我们之前没讲过这个分之,这里我们详细看下,这个分支,就2行代码,第二行,只是简单的修改了Req#http_req记录,就不讲了,重点看下第一行:
Field2 = format_header(Field), 这一行,cowboy_http_protocol:format_header/1 函数如下:
%% @todo While 32 should be enough for everybody, we should probably make %% this configurable or something. -spec format_header(atom()) -> atom(); (binary()) -> binary(). format_header(Field) when is_atom(Field) -> Field; format_header(Field) when byte_size(Field) =< 20; byte_size(Field) > 32 -> Field; format_header(Field) -> format_header(Field, true, <<>>). -spec format_header(binary(), boolean(), binary()) -> binary(). format_header(<<>>, _Any, Acc) -> Acc; %% Replicate a bug in OTP for compatibility reasons when there's a - right %% after another. Proper use should always be 'true' instead of 'not Bool'. format_header(<< $-, Rest/bits >>, Bool, Acc) -> format_header(Rest, not Bool, << Acc/binary, $- >>); format_header(<< C, Rest/bits >>, true, Acc) -> format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_upper(C)) >>); format_header(<< C, Rest/bits >>, false, Acc) -> format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_lower(C)) >>).
这个函数前面2个分支,简单的返回Filed,最后一个分支,会调用 cowboy_http_protocol:format_header/3 函数,主要是转换大小写。
第五个分之:
%% The Host header is required in HTTP/1.1. header(http_eoh, #http_req{version={1, 1}, host=undefined}, State) -> error_terminate(400, State); %% It is however optional in HTTP/1.0. header(http_eoh, Req=#http_req{version={1, 0}, transport=Transport, host=undefined}, State=#state{buffer=Buffer}) -> Port = default_port(Transport:name()), onrequest(Req#http_req{host=[], raw_host= <<>>, port=Port, buffer=Buffer}, State#state{buffer= <<>>}); header(http_eoh, Req, State=#state{buffer=Buffer}) -> onrequest(Req#http_req{buffer=Buffer}, State#state{buffer= <<>>});
我把这三个分支都贴上来,这里跟之前的分支,比较大的不同就是,http_header 变成了 http_eoh。当Header参数的值为以下时,调用匹配这三个分支中的一个:
< Header = http_eoh
< Rest = <<>>
在这个例子中,我们可以看下,include/http.hrl 记录,这里有一些默认值,比如:version = {1, 1} 其他我就不依依列举,大家一看就很明白了,而 host我们在第一个分支会调用该记录设置host的值,这个例子为:[<<"localhost">>],那么这个例子将会调用三个分支中的第三个,也就是:
header(http_eoh, Req, State=#state{buffer=Buffer}) ->
onrequest(Req#http_req{buffer=Buffer}, State#state{buffer= <<>>});
分支里的代码,我这里先不讲,我们留到下一篇,再来详细看。
最后一个分支:
header(_Any, _Req, State) ->
error_terminate(400, State).
当不匹配上面所有分支时,就会匹配这最后一个分支,这个分支的实现就一行代码,调用函数 cowboy_http_protocol:error_terminate/2 函数:
%% Only send an error reply if there is no resp_sent message. -spec error_terminate(cowboy_http:status(), #state{}) -> ok. error_terminate(Code, State=#state{socket=Socket, transport=Transport, onresponse=OnResponse}) -> receive {cowboy_http_req, resp_sent} -> ok after 0 -> _ = cowboy_http_req:reply(Code, #http_req{ socket=Socket, transport=Transport, onresponse=OnResponse, connection=close, pid=self(), resp_state=waiting}), ok end, terminate(State). -spec terminate(#state{}) -> ok. terminate(#state{socket=Socket, transport=Transport}) -> Transport:close(Socket), ok.
这个函数会往客户端返回一个错误号,由于篇幅,我们留到下一篇跟大家一起分享,连同 cowboy_http_protocol:onrequest/2 函数。
最后,谢谢大家支持,大家可以关注我的微博来和我进行互动,哈哈。