这一篇,接着上一篇没有讲完的内容,继续来看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:
这里我简单做了几个测试,大家一看就明白这个函数的意思了。
< 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},
还记得下面这个函数吗,我们在 提到过这个函数:
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_http_protocol:onrequest/2 函数。
大家早点休息,晚安。