Cowboy 源码分析(九)

编程入门 行业动态 更新时间:2024-10-05 23:26:32

Cowboy <a href=https://www.elefans.com/category/jswz/34/1770099.html style=源码分析(九)"/>

Cowboy 源码分析(九)

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

  好了,进入今天的主题,上一篇中,我们讲到了 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 地址给出下,方便大家查看:.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 地址:.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 -> closeend,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 -> closeend,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协议的朋友,可以参考下:.html

   最后,谢谢大家的支持。

 

转载于:.html

更多推荐

Cowboy 源码分析(九)

本文发布于:2024-02-27 21:25:35,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1766374.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:源码   Cowboy

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!