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

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

  在上一篇中,我们整理了下cowboy_http_protocol:header/3函数,在文章的末尾留下2个没有讲到的函数,今天,我们先看下cowboy_http_protocol:error_terminate/2函数,另一个函数下一篇,我们再看。cowboy_http_protocol:error_terminate/2函数定义如下:

%% Only send an error reply if there is no resp_sent message.-spec error_terminate(cowboy_http:status(), #state{}) -> ok.error_terminate(Code, State=#state{socket=Socket, transport=Transport,        onresponse=OnResponse}) ->    receive        {cowboy_http_req, resp_sent} -> ok    after 0 ->        _ = cowboy_http_req:reply(Code, #http_req{            socket=Socket, transport=Transport, onresponse=OnResponse,            connection=close, pid=self(), resp_state=waiting}),        ok    end,    terminate(State).-spec terminate(#state{}) -> ok.terminate(#state{socket=Socket, transport=Transport}) ->    Transport:close(Socket),    ok.

  这个函数,仅仅是给客户端一个错误答复。Code是代表返回的HTTP状态码,具体每个值代表什么意思,大家可以参考下维基百科的 这个需要大家了解下HTTP协议的相关内容。真的很有必要,了解底层的一些协议。建议大家买几本相关的书看看。

  好了,回到逻辑本身,这里有个知识点,摘自《Erlang程序设计》 109页:

  超时时间为0的receive  

   一个超时时间为0的语句会立即触发一个超时,但在此之前,系统会尝试对邮箱进行模式匹配,我们可以利用这个特性来定一个flush_buffer函数,它可以完全清空进程邮箱中的所有消息:

  flush_buffer() ->    receive      _Any ->        flush_buffer()    after 0 ->      true    end.

  好了,回到逻辑中,这个函数会检查进程的邮箱中是否存在 {cowboy_http_req, resp_sent} 消息,如果存在,则返回 ok,紧接着马上触发一个超时,我们来看下,超时中的处理代码:

_ = cowboy_http_req:reply(Code, #http_req{            socket=Socket, transport=Transport, onresponse=OnResponse,            connection=close, pid=self(), resp_state=waiting}),        ok

  这里调用 cowboy_http_req:reply/2 函数,并且忽略返回值,紧接着返回 ok,这里我们来重点看下这个函数:

%% @equiv reply(Status, [], [], Req)-spec reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}.reply(Status, Req=#http_req{resp_body=Body}) ->    reply(Status, [], Body, Req).%% @equiv reply(Status, Headers, [], Req)-spec reply(cowboy_http:status(), cowboy_http:headers(), #http_req{})    -> {ok, #http_req{}}.reply(Status, Headers, Req=#http_req{resp_body=Body}) ->    reply(Status, Headers, Body, Req).%% @doc Send a reply to the client.-spec reply(cowboy_http:status(), cowboy_http:headers(), iodata(), #http_req{})    -> {ok, #http_req{}}.reply(Status, Headers, Body, Req=#http_req{socket=Socket, transport=Transport,        version=Version, connection=Connection,        method=Method, resp_state=waiting, resp_headers=RespHeaders}) ->    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), if Method =:= 'HEAD' -> ok; ReplyType =:= hook -> ok; %% Hook replied for us, stop there. true -> case Body of {_, StreamFun} -> StreamFun(); _ -> Transport:send(Socket, Body) end end, {ok, Req2#http_req{connection=RespConn, resp_state=done, resp_headers=[], resp_body= <<>>}}.

  不管是 reply/2,还是reply/3最后都是调用reply/4函数,这个函数是给客户端发一个回复,代码量相对多些,我们来详细看下:

  我们看下,函数参数:参数Status就是Code,也就是HTTP状态码,Headers为空列表 [],Req=#http_req{resp_body=Body},这里的Body为默认值 resp_body  = <<>>,Sokcet的值为连接到服务器的连接,Transport 为cowboy_tcp_transport,Version 值为 {1,1},其他:Connection = keepalive,Method = 'GET',RespHeaders = [],有些值是默认值,大家可以看下记录的定义。

  来看具体逻辑:

  RespConn = response_connection(Headers, Connection), 这里调用cowboy_http_req:response_connection/2函数,函数代码如下:

-spec response_connection(cowboy_http:headers(), keepalive | close)    -> keepalive | close.response_connection([], Connection) ->    Connection;response_connection([{Name, Value}|Tail], Connection) ->    case Name of        'Connection' -> response_connection_parse(Value);        Name when is_atom(Name) -> response_connection(Tail, Connection);        Name ->            Name2 = cowboy_bstr:to_lower(Name),            case Name2 of                <<"connection">> -> response_connection_parse(Value);                _Any -> response_connection(Tail, Connection)            end    end.

  这里Headers为空列表 [],所以这里只返回了Connection状态,也就是keepalive,当参数Headers不为空列表时,会走下面的分支,这里判断Name的值,如果为'Connection',则调用cowboy_http_req:response_connection_parse/1函数,代码如下:

-spec response_connection_parse(binary()) -> keepalive | close.response_connection_parse(ReplyConn) ->    Tokens = cowboy_http:nonempty_list(ReplyConn, fun cowboy_http:token/2),    cowboy_http:connection_to_atom(Tokens).

  我在和 很详细的看了cowboy_http:nonempty_list/2cowboy_http:token/2这两个函数,同样的,我们在 也讲过ConnAtom = cowboy_http:connection_to_atom(ConnTokens)这个函数,这里就不重复看了,大家如果忘了,可以点链接回忆下,温故而知新,还有个函数cowboy_bstr:to_lower/1更简单,也不打算讲。

  我们接着看 ContentLen = case Body of {CL, _} -> CL; _ -> iolist_size(Body) end, 这里如果Body的值为{CL, _}格式,则ContentLen的值为CL,否则为iolist_size(Body)。这个函数我们第一次遇到,看下 erlang doc,地址:。比较简单,大家也可以看下这篇文章,坚强2002同学,很详细的讲解了iolist:,我自己也做了些简单的练习,如图:

  

  好了,这个函数,大家也好好理解下,我们接着往下看:  

HTTP11Headers = case Version of        {
1, 1} -> [{<<"Connection">>, atom_to_connection(Connection)}]; _ -> [] end,

  构建,HTTP 1.1 Header,如果HTTP协议版本为{1, 1},则返回,[{<<"Connection">>, atom_to_connection(Connection)}];这里,我们看下cowboy_http_req:atom_to_connection/1函数:

-spec atom_to_connection(keepalive) -> <<_:80>>;                        (close) -> <<_:40>>.atom_to_connection(keepalive) ->    <<"keep-alive">>;atom_to_connection(close) ->    <<"close">>.

  一看就能明白,不解释了,HTTP11Headers这个变量值可能为[{<<"Connection">>, <<"keep-alive">>}]或者[{<<"Connection">>, <<"close">>}]。

  由于篇幅太长,今天我们就看到这里,下一篇,我们继续从下面这一行开始:

{ReplyType, Req2} = response(Status, Headers, RespHeaders,  [        {
<<"Content-Length">>, integer_to_list(ContentLen)}, {
<<"Date">>, cowboy_clock:rfc1123()}, {
<<"Server">>, <<"Cowboy">>} |HTTP11Headers], Req),

   最后,谢谢大家支持,晚安。

  

  2012-06-25补充:

  在翻看这篇文章和之后的文章时,发现漏讲了cowboy_http_req:reply/4 函数部分代码,故补充在这里:

if    Method =:= 'HEAD' -> ok;        ReplyType =:= hook -> ok; %% Hook replied for us, stop there.        true ->            case Body of                {_, StreamFun} -> StreamFun();                _ -> Transport:send(Socket, Body)            end    end,    {ok, Req2#http_req{connection=RespConn, resp_state=done,        resp_headers=[], resp_body= <<>>}}.

  这其实也比较简单,这里判断Method 是否全等于 'HEAD',如果是返回 ok;ReplyType 是否全等于hook,如果是返回ok;否则,根据Body情况进行匹配,这里如果是返回HTTP状态码,也就是Body为[],则都不匹配,如果 Body = <<"Hello world!">>,则把Body发送给连接到服务器的Socket。

  最后,修改了connection的值为RespConn,resp_state为done,其他就不解释了,都能看的懂。

  很抱歉,之前写完,并没有很好的检查。

  

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

你可能感兴趣的文章
[水]三个数学的小技巧题
查看>>
mysql中查看数据库的版本,什么版本
查看>>
[leetcode-342-Power of Four]
查看>>
MongoDB3.0 创建用户
查看>>
2017-2018-1 20155319 《信息安全系统设计基础》第3周学习总结
查看>>
express 3.0.x 中默认不支持flash() 的解决方法
查看>>
uva-111-dp
查看>>
算法学习1——矩阵转置
查看>>
Tcl与Design Compiler (九)——综合后的形式验证
查看>>
跨页数据传递
查看>>
Linux查看系统负载(CPU和MEM考虑)
查看>>
Codeforces Round #249 (Div. 2) B. Pasha Maximizes
查看>>
MyEclipse 2015 Stable 2.0破解方法
查看>>
【Android游戏开发十一】手把手让你爱上Android sdk自带“9妹”(9patch 工具),让Android游戏开发更方便!...
查看>>
【查找算法】基于存储的查找算法(哈希查找)
查看>>
JavaWeb网上图书商城完整项目--day02-10.提交注册表单功能之页面实现
查看>>
Tomcat组件梳理--Server
查看>>
记录一下这次web实训的两个网站
查看>>
POJ-1830 开关问题 高斯消元
查看>>
HDU-4366 Successor 线段树+预处理
查看>>