大家好,上一篇我们总结了cowboy_examples这个例子启动时,进程的启动总结,这一篇,我们看下cowboy如何响应来自浏览器的请求。
首先我们看下,在上一篇cowboy_acceptors_sup启动的100个cowboy_acceptor工作进程,而这个工作进程在初始化时,同时等待来自浏览器的Socket连接,代码如下:
-spec start_link(inet:socket(), module(), module(), any(), pid(), pid()) -> {ok, pid()}. start_link(LSocket, Transport, Protocol, Opts, ListenerPid, ReqsSup) -> Pid = spawn_link(?MODULE, acceptor, [LSocket, Transport, Protocol, Opts, 1, ListenerPid, ReqsSup]), {ok, Pid}. %% Internal. -spec acceptor(inet:socket(), module(), module(), any(), non_neg_integer(), pid(), pid()) -> no_return(). acceptor(LSocket, Transport, Protocol, Opts, OptsVsn, ListenerPid, ReqsSup) -> Res = case Transport:accept(LSocket, 2000) of {ok, CSocket} -> {ok, Pid} = supervisor:start_child(ReqsSup, [ListenerPid, CSocket, Transport, Protocol, Opts]), Transport:controlling_process(CSocket, Pid), cowboy_listener:add_connection(ListenerPid, default, Pid, OptsVsn); {error, timeout} -> cowboy_listener:check_upgrades(ListenerPid, OptsVsn); {error, _Reason} -> %% @todo Probably do something here. If the socket was closed, %% we may want to try and listen again on the port? ok end, case Res of ok -> ?MODULE:acceptor(LSocket, Transport, Protocol, Opts, OptsVsn, ListenerPid, ReqsSup); {upgrade, Opts2, OptsVsn2} -> ?MODULE:acceptor(LSocket, Transport, Protocol, Opts2, OptsVsn2, ListenerPid, ReqsSup) end.
这里调用cowboy_tcp_transport:accept/2等待一个Socket连接上,如果有Socket连接上,就会添加一个子进程到ReqsSup这棵树下,代码如下:{ok, Pid} = supervisor:start_child(ReqsSup, [ListenerPid, CSocket, Transport, Protocol, Opts]),函数如下:
-spec start_request(pid(), inet:socket(), module(), module(), any()) -> {ok, pid()}. start_request(ListenerPid, Socket, Transport, Protocol, Opts) -> Protocol:start_link(ListenerPid, Socket, Transport, Opts).
这个函数调用 cowboy_http_protocol:start_link/4 函数代码如下:
%% @doc Start an HTTP protocol process. -spec start_link(pid(), inet:socket(), module(), any()) -> {ok, pid()}. start_link(ListenerPid, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport, Opts]), {ok, Pid}.
如果没有获取Sockt连接超时2秒,则调用cowboy_listener:check_upgrades(ListenerPid, OptsVsn),然后继续递归调用?MODULE:acceptor(LSocket, Transport, Protocol, Opts, OptsVsn, ListenerPid, ReqsSup),这里跟我之前理解的是有偏差的,之前我没注意到这个超时处理和递归,我一直认为代码会一直停留在 Res = case Transport:accept(LSocket, 2000) of 这一行,知道有Socket连接上,完全没注意到超时处理,这里提醒下大家吧,我们可以注意下下图,这个模块一直是running,这里我把之前的100个子进程,修改成1个了,所以图上就1个running的cowboy_acceptor模块。
然后我们看下这行代码cowboy_tcp_transport:controlling_process/2,代码如下:
%% @doc Assign a new controlling process <em>Pid</em> to <em>Socket</em>. %% @see gen_tcp:controlling_process/2 -spec controlling_process(inet:socket(), pid()) -> ok | {error, closed | not_owner | atom()}. controlling_process(Socket, Pid) -> gen_tcp:controlling_process(Socket, Pid).
这个函数的作用是把一个套接字的控制进程改为新的控制进程Pid,之前详细讲过,这里不重复。
最后调用函数 cowboy_listener:add_connection/4,代码如下:
-spec add_connection(pid(), atom(), pid(), non_neg_integer()) -> ok | {upgrade, any(), non_neg_integer()}. add_connection(ServerPid, Pool, ConnPid, OptsVsn) -> gen_server:call(ServerPid, {add_connection, Pool, ConnPid, OptsVsn}, infinity).
添加连接到cowboy_listener,具体的逻辑我们就不分析了,之前说过了。
还有个重点就是,关于自定义handler的调用,主要的代码都在 cowboy_http_protocol 模块中,一共有4个函数:
调用自定义handler:init/3函数:
-spec handler_init(#http_req{}, #state{}) -> ok. handler_init(Req, State=#state{transport=Transport, handler={Handler, Opts}}) -> try Handler:init({Transport:name(), http}, Req, Opts) of {ok, Req2, HandlerState} -> handler_handle(HandlerState, Req2, State); {loop, Req2, HandlerState} -> handler_before_loop(HandlerState, Req2, State); {loop, Req2, HandlerState, hibernate} -> handler_before_loop(HandlerState, Req2, State#state{hibernate=true}); {loop, Req2, HandlerState, Timeout} -> handler_before_loop(HandlerState, Req2, State#state{loop_timeout=Timeout}); {loop, Req2, HandlerState, Timeout, hibernate} -> handler_before_loop(HandlerState, Req2, State#state{hibernate=true, loop_timeout=Timeout}); {shutdown, Req2, HandlerState} -> handler_terminate(HandlerState, Req2, State); %% @todo {upgrade, transport, Module} {upgrade, protocol, Module} -> upgrade_protocol(Req, State, Module) catch Class:Reason -> error_terminate(500, State), PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))), error_logger:error_msg( "** Handler ~p terminating in init/3~n" " for the reason ~p:~p~n" "** Options were ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [Handler, Class, Reason, Opts, PLReq, erlang:get_stacktrace()]) end.
调用自定义handler:handle/2函数:
-spec handler_handle(any(), #http_req{}, #state{}) -> ok. handler_handle(HandlerState, Req, State=#state{handler={Handler, Opts}}) -> try Handler:handle(Req, HandlerState) of {ok, Req2, HandlerState2} -> terminate_request(HandlerState2, Req2, State) catch Class:Reason -> ......end.
调用自定义handler:info/3函数:
-spec handler_call(any(), #http_req{}, #state{}, any()) -> ok. handler_call(HandlerState, Req, State=#state{handler={Handler, Opts}}, Message) -> try Handler:info(Message, Req, HandlerState) of {ok, Req2, HandlerState2} -> terminate_request(HandlerState2, Req2, State); {loop, Req2, HandlerState2} -> handler_before_loop(HandlerState2, Req2, State); {loop, Req2, HandlerState2, hibernate} -> handler_before_loop(HandlerState2, Req2, State#state{hibernate=true}) catch Class:Reason -> ......end.
调用自定好handler:terminate/2函数:
-spec handler_terminate(any(), #http_req{}, #state{}) -> ok. handler_terminate(HandlerState, Req, #state{handler={Handler, Opts}}) -> try Handler:terminate(Req#http_req{resp_state=locked}, HandlerState) catch Class:Reason -> ......end.
关于分解请求包和构建响应包,这里不打算重复讲了,因为涉及的函数比较多,也没什么特别的地方。
好了,这个系列终于结束了,花了很多时间在cowboy项目上,也学到了很多,之后相信还会有机会再跟大家分享cowboy的源码,因为我下一个打算看的项目是mochiweb,也是基于http协议的erlang应用,可能到时候会对比下这两个项目吧。
这里,也希望博客园的朋友,尝试下Erlang这门语言吧,多会一门语言对你的编程人生还是有帮助的,至少你可以了解另一个编程世界的不同思想。
谢谢大家的耐心收看,也欢迎大家和我交流,可以关注我的微博,也可以直接给我留言。