Cowboy 源码分析(十九)
2012-06-20 01:18 rhinovirus 阅读(1395) 评论(0) 编辑 收藏 举报这一篇,接着上一篇没有讲完的内容,继续来看cowboy_http_req:reply/4 函数,我们从下面这段代码开始:
RespConn = response_connection(Headers, Connection), ContentLen = case Body of {CL, _} -> CL; _ -> iolist_size(Body) end, HTTP11Headers = case Version of {1, 1} -> [{<<"Connection">>, atom_to_connection(Connection)}]; _ -> [] end, {ReplyType, Req2} = response(Status, Headers, RespHeaders, [ {<<"Content-Length">>, integer_to_list(ContentLen)}, {<<"Date">>, cowboy_clock:rfc1123()}, {<<"Server">>, <<"Cowboy">>} |HTTP11Headers], Req),
这几行,作用就是构建HTTP响应头,如下图:
我们看下最后几行,调用了cowboy_http_req:response/5函数:
-spec response(cowboy_http:status(), cowboy_http:headers(), cowboy_http:headers(), cowboy_http:headers(), #http_req{}) -> {normal | hook, #http_req{}}. response(Status, Headers, RespHeaders, DefaultHeaders, Req=#http_req{ socket=Socket, transport=Transport, version=Version, pid=ReqPid, onresponse=OnResponse}) -> FullHeaders = response_merge_headers(Headers, RespHeaders, DefaultHeaders), Req2 = case OnResponse of undefined -> Req; OnResponse -> OnResponse(Status, FullHeaders, %% Don't call 'onresponse' from the hook itself. Req#http_req{resp_headers=[], resp_body= <<>>, onresponse=undefined}) end, ReplyType = case Req2#http_req.resp_state of waiting -> HTTPVer = cowboy_http:version_to_binary(Version), StatusLine = << HTTPVer/binary, " ", (status(Status))/binary, "\r\n" >>, HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>] || {Key, Value} <- FullHeaders], Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>]), ReqPid ! {?MODULE, resp_sent}, normal; _ -> hook end, {ReplyType, Req2}.
首先,FullHeaders = response_merge_headers(Headers, RespHeaders, DefaultHeaders),这里我们可以看下,这几个参数的值:
< Headers = []
< RespHeaders = []
< DefaultHeaders = [{<<"Content-Length">>,"12"},
{<<"Date">>,<<"Tue, 19 Jun 2012 15:28:24 GMT">>},
{<<"Server">>,<<"Cowboy">>},
{<<"Connection">>,<<"keep-alive">>}]
继续,来看下cowboy_http_req:response_merge_headers/3函数,定义如下:
-spec response_merge_headers(cowboy_http:headers(), cowboy_http:headers(), cowboy_http:headers()) -> cowboy_http:headers(). response_merge_headers(Headers, RespHeaders, DefaultHeaders) -> Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers], merge_headers( merge_headers(Headers2, RespHeaders), DefaultHeaders).
这里有个函数,cowboy_http_req:header_to_binary/1代码如下,这里我只列出了部分,由于代码量比较大,而且又很简单:
-spec header_to_binary(cowboy_http:header()) -> binary(). header_to_binary('Cache-Control') -> <<"Cache-Control">>; header_to_binary('Connection') -> <<"Connection">>; header_to_binary('Date') -> <<"Date">>; ...
这个函数,把对应的header字符串转为二进制的格式,知道这个函数的作用,就可以继续看cowboy_http_req:response_merge_headers/3函数:
Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers], 这一行,把Headers中的Key,转为二进制格式,组成新的头部,很简单,但是由于Headers = [],那么其实这里最后还是[]。
merge_headers( merge_headers(Headers2, RespHeaders), DefaultHeaders). 这一行,调用了两次 cowboy_http_req:merge_headers/2函数,函数定义如下:
-spec merge_headers(cowboy_http:headers(), cowboy_http:headers()) -> cowboy_http:headers(). merge_headers(Headers, []) -> Headers; merge_headers(Headers, [{Name, Value}|Tail]) -> Headers2 = case lists:keymember(Name, 1, Headers) of true -> Headers; false -> Headers ++ [{Name, Value}] end, merge_headers(Headers2, Tail).
第一次调用这个函数,传递参数,Headers2, RespHeaders 我们从上面知道,这2个参数均为[],那么第一次调用,则调用第一个分支,返回 Headers = Headers2 = []。
第二次调用传递参数,merge_headers(Headers2, RespHeaders) 返回的[],作为第一个参数,DefaultHeaders则为第二个参数,这里我把几个参数的值列出来,方便理解:
< Name = <<"Content-Length">>
< Headers = []
< Value = "12"
< Tail = [{<<"Date">>,<<"Tue, 19 Jun 2012 15:53:16 GMT">>},
{<<"Server">>,<<"Cowboy">>},
{<<"Connection">>,<<"keep-alive">>}]
lists:keymember/3 这个函数,我们看下 erlang doc:http://www.erlang.org/doc/man/lists.html#keymember-3
这里我简单做了几个测试,大家一看就明白这个函数的意思了。
最后,我们能得出如下结果:
< Headers2 = [{<<"Content-Length">>,"12"}]
最后,这个函数,是个尾递归函数:merge_headers(Headers2, Tail).
当Tail = []时,函数回到第一个分支:
< Headers = [{<<"Content-Length">>,"12"},
{<<"Date">>,<<"Tue, 19 Jun 2012 16:22:57 GMT">>},
{<<"Server">>,<<"Cowboy">>},
{<<"Connection">>,<<"keep-alive">>}]
好了,这个cowboy_http_req:response_merge_headers/3函数我们可以告一段落了,接下来回到cowboy_http_req:response/5函数的第二段代码:
Req2 = case OnResponse of undefined -> Req; OnResponse -> OnResponse(Status, FullHeaders, %% Don't call 'onresponse' from the hook itself. Req#http_req{resp_headers=[], resp_body= <<>>, onresponse=undefined}) end,
这里,我们从上下文知道: onresponse=OnResponse=undefined,所以这里返回 Req2 = Req,而另一个分支,等以后具体用到的时候,我再分析,这里就不讲了,下面是Req的值:
< Req = {http_req,#Port<0.3005>,cowboy_tcp_transport,keepalive,<0.527.0>,
'GET',
{1,1},
undefined,
[<<"localhost">>],
undefined,<<"localhost">>,8080,[],undefined,<<"/">>,
undefined,<<>>,[],
[{'Connection',<<"keep-alive">>},
{'Accept-Encoding',<<"gzip, deflate">>},
{'Accept-Language',<<"zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3">>},
{'Accept',<<"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8">>},
{'User-Agent',<<"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0">>},
{'Host',<<"localhost">>}],
[{'Connection',[<<"keep-alive">>]}],
undefined,[],waiting,<<>>,waiting,[],<<>>,undefined,
{#Fun<cowboy_http.urldecode.2>,crash}}
继续看cowboy_http_req:response/5函数的第三段代码:
ReplyType = case Req2#http_req.resp_state of waiting -> HTTPVer = cowboy_http:version_to_binary(Version), StatusLine = << HTTPVer/binary, " ", (status(Status))/binary, "\r\n" >>, HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>] || {Key, Value} <- FullHeaders], Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>]), ReqPid ! {?MODULE, resp_sent}, normal; _ -> hook end,
这里,我们使用Debugger可以知道,ReplyType = waiting,那么继续往下看:
HTTPVer = cowboy_http:version_to_binary(Version), 这里根据版本,返回对应二进制格式,具体代码如下:
%% @doc Convert an HTTP version tuple to its binary form. -spec version_to_binary(version()) -> binary(). version_to_binary({1, 1}) -> <<"HTTP/1.1">>; version_to_binary({1, 0}) -> <<"HTTP/1.0">>.
StatusLine = << HTTPVer/binary, " ", (status(Status))/binary, "\r\n" >>,
这里生成状态行,如下图:
cowboy_http_req:status/1函数如下,这个函数也比较大,我省略了大部分:
-spec status(cowboy_http:status()) -> binary(). status(100) -> <<"100 Continue">>; status(101) -> <<"101 Switching Protocols">>; status(102) -> <<"102 Processing">>; status(200) -> <<"200 OK">>; ...
HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>] || {Key, Value} <- FullHeaders], 这个跟上面差不多意思,如下图:
Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>]),
=cowboy_tcp_transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>]), 这个函数代码如下:
%% @doc Send a packet on a socket. %% @see gen_tcp:send/2 -spec send(inet:socket(), iolist()) -> ok | {error, atom()}. send(Socket, Packet) -> gen_tcp:send(Socket, Packet).
代码如此简单,以至于我都不用解释了,这里给连接到服务器的连接Socket发送一个数据包。
ReqPid ! {?MODULE, resp_sent},
还记得下面这个函数吗,我们在Cowboy 源码分析(十) 提到过这个函数:
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);
就是在这里,我们设置的 #http_req.pid = self(),而我们在上面那个函数的最后,给这个处理请求的进程,发送了一个消息 {?MODULE, resp_sent},
发送完消息后,上面那个函数返回 normal。
好了,今天的篇幅比较长,就到这吧,下一篇,我们看下,处理请求的进程如何处理这个消息,以及 Cowboy 源码分析(十七) 提到的cowboy_http_protocol:onrequest/2 函数。
大家早点休息,晚安。
本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名rhinovirus(包含链接http://www.cnblogs.com/rhinovirus/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系。