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是可以预知的总大小的包或如果预期的数据包的大小是不知道则为undefined。decode_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
最后,谢谢大家的支持。
本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名rhinovirus(包含链接http://www.cnblogs.com/rhinovirus/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系。