代码改变世界

Cowboy 源码分析(十七)

2012-06-13 00:54  rhinovirus  阅读(1680)  评论(0编辑  收藏  举报

  大家好,今天继续跟大家分享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 函数。

  最后,谢谢大家支持,大家可以关注我的微博来和我进行互动,哈哈。