代码改变世界

Cowboy 源码分析(九)

2012-05-27 23:37  rhinovirus  阅读(2624)  评论(0编辑  收藏  举报

  大家好,今天是周日,继续为大家带来这个系列的第九篇,昨天参加同事的婚礼,今年已经好几个同事结婚了,时间过的真是快啊。转眼也是奔三的人了,得再加把劲学习了。

  好了,进入今天的主题,上一篇中,我们讲到了 cowboy_http_protocol:wait_request/1 和 cowboy_listener:add_pid/4 这两个方法,今天,我们继续。

  我们先看下调用 cowboy_http_protocol:wait_request/1 这个方法cowboy_http_protocol:init/1的代码:  

%% @private
-spec init(pid(), inet:socket(), module(), any()) -> ok.
init(ListenerPid, Socket, Transport, Opts) ->
    Dispatch = proplists:get_value(dispatch, Opts, []),
    MaxEmptyLines = proplists:get_value(max_empty_lines, Opts, 5),
    MaxKeepalive = proplists:get_value(max_keepalive, Opts, infinity),
    MaxLineLength = proplists:get_value(max_line_length, Opts, 4096),
    OnRequest = proplists:get_value(onrequest, Opts),
    OnResponse = proplists:get_value(onresponse, Opts),
    Timeout = proplists:get_value(timeout, Opts, 5000),
    URLDecDefault = {fun cowboy_http:urldecode/2, crash},
    URLDec = proplists:get_value(urldecode, Opts, URLDecDefault),
    ok = cowboy:accept_ack(ListenerPid),
    wait_request(#state{listener=ListenerPid, socket=Socket, transport=Transport,
        dispatch=Dispatch, max_empty_lines=MaxEmptyLines,
        max_keepalive=MaxKeepalive, max_line_length=MaxLineLength,
        timeout=Timeout, onrequest=OnRequest, onresponse=OnResponse,
        urldecode=URLDec}).

  还记得之前的第七篇文章,我们提到 Opts = [{dispatch, Dispatch}]。那么除了 Dispatch 的值,其他的参数均是默认值。这里就不一一列出来了,这些参数在以后具体用的时候,我再介绍参数的作用。我们继续看 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} -> parse_request(State#state{
            buffer= << Buffer/binary, Data/binary >>});
        {error, _Reason} -> terminate(State)
    end.

  Socket = LSocket 是当有客户端连接到 8080 端口时,{ok, LSocket} = cowboy_tcp_transport:listen([{port, 8080}]) 这里返回的LSokcet。

  Transport = cowboy_tcp_transport;

  T = 5000;

  Buffer = buffer = <<>> :: binary(),

  好了,接下来,我们看下:

  case Transport:recv(Socket, 0, T) of

  = case cowboy_tcp_transport:recv(Socket, 0, T) of

  我们来看下 cowboy_tcp_transport:recv/3 这个方法:

%% @doc Receive a packet from a socket in passive mode.
%% @see gen_tcp:recv/3
-spec recv(inet:socket(), non_neg_integer(), timeout())
    -> {ok, any()} | {error, closed | atom()}.
recv(Socket, Length, Timeout) ->
    gen_tcp:recv(Socket, Length, Timeout).

  这个方法很简单,调用 gen_tcp:recv/3 从指定 Socket中,读取Length长度的消息,Timeout为超时时间。但是,我们看到,这里给出的 Length = 0,If Length = 0, all available bytes are returned. 这是官方文档给出的解释,如果长度为 0,则返回Socket所有的有效的字节。

  最后我也把 erlang doc 地址给出下,方便大家查看:http://www.erlang.org/doc/man/gen_tcp.html

  好了,我们继续回到 cowboy_http_protocol:wait_request/1 这个方法,看下:

        {ok, Data} -> parse_request(State#state{
            buffer= << Buffer/binary, Data/binary >>});
        {error, _Reason} -> terminate(State)

  如果 cowboy_tcp_transport:recv/3 返回 {ok, Data}, 则调用 parse_request(State#state{ buffer= << Buffer/binary, Data/binary >>}); 方法,如果返回 {error, _Reason} 则调用 terminate(State) 方法。

  我们先看下 cowboy_http_protocol:terminate/1这个方法:  

-spec terminate(#state{}) -> ok.
terminate(#state{socket=Socket, transport=Transport}) ->
    Transport:close(Socket),
    ok.

  cowboy_tcp_transport:close/1 这个方法很简单,调用 gen_tcp:close/1 关闭Socket,代码如下:

%% @doc Close a TCP socket.
%% @see gen_tcp:close/1
-spec close(inet:socket()) -> ok.
close(Socket) ->
    gen_tcp:close(Socket).

  好了,这个方法比较简单,接下来看下 cowboy_http_protocol:parse_request/1 这个方法,解析从Socket读取到的所有字节,代码如下:  

%% @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.

  case erlang:decode_packet(http_bin, Buffer, []) of 我们看下这行,erlang doc 地址:http://www.erlang.org/doc/man/erlang.html#decode_packet-3

官方说明如下:

  Decodes the binary Bin according to the packet protocol specified by Type. Very similar to the packet handling done by sockets with the option {packet,Type}.

  通过指定的数据包协议类型Type解码二进制Bin。对sockets指定选项 {packet,Type} 处理相似的数据包。

  If an entire packet is contained in Bin it is returned together with the remainder of the binary as {ok,Packet,Rest}.

  如果 Bin 包含一个完整的数据包,其余的二进制也将一起返回,通过 {ok,Packet,Rest}

  If Bin does not contain the entire packet, {more,Length} is returned. Length is either the expected total size of the packet or undefined if the expected packet size is not known. decode_packet can then be called again with more data added.

  如果Bin没有包含一个完整的数据包,会返回 {more,Length}。Length是可以预知的总大小的包或如果预期的数据包的大小是不知道则为undefineddecode_packet 然后可以再次被调用添加更多的数据。

  If the packet does not conform to the protocol format {error,Reason} is returned.

  如果数据包不符合该协议的格式返回 {error,Reason}

  好了,现在我们知道这个方法是通过指定的协议类型,来解码二进制数据Bin了。如果Bin包含完整的数据包,就返回 {ok, Request, Rest},Rest 是剩余的二进制。如果不包含

完整的的数据包,则返回 {more,Length}

  http_bin 这个类型是 http的变种,返回的字符串将被二进制列表代替。

  好了,对这个方法解释,就到这了,我们来看下这行:

  {ok, Request, Rest} -> request(Request, State#state{buffer=Rest});

  我们把剩余的二进制数据,保存在 State#state{buffer=Rest} 中,并且调用 cowboy_http_protocol:request/2 处理请求方法:

-spec request({http_request, cowboy_http:method(), cowboy_http:uri(),
    cowboy_http:version()}, #state{}) -> ok.
request({http_request, _Method, _URI, Version}, State)
        when Version =/= {1, 0}, Version =/= {1, 1} ->
    error_terminate(505, State);
%% We still receive the original Host header.
request({http_request, Method, {absoluteURI, _Scheme, _Host, _Port, Path},
        Version}, State) ->
    request({http_request, Method, {abs_path, Path}, Version}, State);
request({http_request, Method, {abs_path, AbsPath}, Version},
        State=#state{socket=Socket, transport=Transport,
        req_keepalive=Keepalive, max_keepalive=MaxKeepalive,
        onresponse=OnResponse, urldecode={URLDecFun, URLDecArg}=URLDec}) ->
    URLDecode = fun(Bin) -> URLDecFun(Bin, URLDecArg) end,
    {Path, RawPath, Qs} = cowboy_dispatcher:split_path(AbsPath, URLDecode),
    ConnAtom = if Keepalive < MaxKeepalive -> version_to_connection(Version);
        true -> close
    end,
    parse_header(#http_req{socket=Socket, transport=Transport,
        connection=ConnAtom, pid=self(), method=Method, version=Version,
        path=Path, raw_path=RawPath, raw_qs=Qs, onresponse=OnResponse,
        urldecode=URLDec}, State);
request({http_request, Method, '*', Version},
        State=#state{socket=Socket, transport=Transport,
        req_keepalive=Keepalive, max_keepalive=MaxKeepalive,
        onresponse=OnResponse, urldecode=URLDec}) ->
    ConnAtom = if Keepalive < MaxKeepalive -> version_to_connection(Version);
        true -> close
    end,
    parse_header(#http_req{socket=Socket, transport=Transport,
        connection=ConnAtom, pid=self(), method=Method, version=Version,
        path='*', raw_path= <<"*">>, raw_qs= <<>>, onresponse=OnResponse,
        urldecode=URLDec}, State);
request({http_request, _Method, _URI, _Version}, State) ->
    error_terminate(501, State);
request({http_error, <<"\r\n">>},
        State=#state{req_empty_lines=N, max_empty_lines=N}) ->
    error_terminate(400, State);
request({http_error, <<"\r\n">>}, State=#state{req_empty_lines=N}) ->
    parse_request(State#state{req_empty_lines=N + 1});
request(_Any, State) ->
    error_terminate(400, State).

  看到这里,我有点吃力了,应该是对 HTTP协议理解不够,我尝试找了下 Ubuntu 下的 类似 Fiddler 的工具,没有找到,我再多找找,也麻烦知道的朋友,留言告诉下我,谢谢。  

  在下一篇中,我将结合工具来给大家比较好的讲解这个方法。这里需要大家好好看下 HTTP 协议,这里我搜了下,刚好有位园友介绍的不错,我把地址贴过来了,不熟悉HTTP协议的朋友,可以参考下:http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html

   最后,谢谢大家的支持。