博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Cowboy 源码分析(十九)
阅读量:7188 次
发布时间:2019-06-29

本文共 8159 字,大约阅读时间需要 27 分钟。

  这一篇,接着上一篇没有讲完的内容,继续来看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 函数。

  大家早点休息,晚安。

  

转载地址:http://lkykm.baihongyu.com/

你可能感兴趣的文章
网络工具 Netcat 之端口扫描
查看>>
参观迅达云成公司观后感
查看>>
如何在一台服务器上实现多个Web站点
查看>>
ubantu16.04安装配置samba服务(原创)
查看>>
DB2数据库代码页和实例代码页的区别(解决DB2乱码问题)
查看>>
结合超声计数炎症关节的改良版DAS28的临床应用
查看>>
如何用BarTender 2016字处理器完成表格设计
查看>>
JSON数据格式
查看>>
页面引入(include)方式的研究及性能比较
查看>>
文件操作
查看>>
前端模板基础-1
查看>>
【转】中间代码opcode的执行
查看>>
android sdk
查看>>
快速幂取模
查看>>
centos7/centos6修改系统默认语言
查看>>
Ubuntu中设置环境变量详解
查看>>
SQL Server性能优化(2)获取基本信息
查看>>
案例二(构建双主高可用HAProxy负载均衡系统)
查看>>
Memcache安装
查看>>
Session 和 Cookie
查看>>