Cowboy 源码分析(二)
2012-05-16 00:13 rhinovirus 阅读(6201) 评论(1) 编辑 收藏 举报大家好,在上一篇文章中,我们简单介绍了cowboy的其中2个文件,分别是 cowboy.app.src 应用程序资源文件(配置文件) 和 cowboy_app.erl 文件,今天,我们继续往下走,昨天的 cowboy_app.erl 中start/2 方法中有这一句,cowboy_sup:start_link(). 那么我们接下来就看下这个module。
代码如下:
%% @private -module(cowboy_sup). -behaviour(supervisor). -export([start_link/0]). %% API. -export([init/1]). %% supervisor. -define(SUPERVISOR, ?MODULE). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). %% supervisor. -spec init([]) -> {'ok', {{'one_for_one', 10, 10}, [{ any(), {atom() | tuple(), atom(), 'undefined' | [any()]}, 'permanent' | 'temporary' | 'transient', 'brutal_kill' | 'infinity' | non_neg_integer(), 'supervisor' | 'worker', 'dynamic' | [atom() | tuple()]}] }}. init([]) -> Procs = [{cowboy_clock, {cowboy_clock, start_link, []}, permanent, 5000, worker, [cowboy_clock]}], {ok, {{one_for_one, 10, 10}, Procs}}.
如果你看了,我推荐的 Erlang OTP 设计原则,那么这个module一定不会陌生,它是Supervisor行为的实现,也就是启动一个监督进程,下面大概看下 Supervisor的
简单介绍(摘抄自Erlang OTP 设计原则):
督程负责启动、停止和监视它的子进程。督程的基本思想是它要保持它的子进程有效,必要的时候可以重启他们。
要启动和监视的子进程由一个 子进程规格 的列表来指定。子进程按照在这个列表中的顺序启动,并且按照相反的顺序终止。
当我们 supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). 这个监督进程的内部会调用 init/1 方法,这个方法需要 需要返回一个元组,我把 OTP 设计原则
里的详细描述贴过来了。这边对返回的一些字段的作用解释了相当清楚,如下:
{Id, StartFunc, Restart, Shutdown, Type, Modules}
Id = term()
StartFunc = {M, F, A}
M = F = atom()
A = [term()]
Restart = permanent | transient | temporary
Shutdown = brutal_kill | integer() >=0 | infinity
Type = worker | supervisor
Modules = [Module] | dynamic
Module = atom()
- Id 是督程内部用于标识子进程规范的名称。
- StartFunc 定义了用于启动子进程的很难书调用。它是一个模块.函数.参数的元组,与 apply(M, F, A) 用的一样。
- Restart定义了一个被终止的子进程要在何时被重启:
- permanent 子进程总会被重启。
- temporary 子进程从不会被重启。
- transient 子进程只有当其被异常终止时才会被重启,即,退出理由不是 normal 。
- Shutdown定义了一个子进程应如何被终止。
- brutal_kill 表示子进程应使用 exit(Child, kill) 进行无条件终止。
- 一个整数超时值表示督程先通过调用 exit(Child, shutdown) 告诉子进程要终止了,然后等待其返回退出信号。如果在指定的事件内没有接受到任何退出信号,那么使用 exit(Child, kill) 无条件终止子进程。
- 如果子进程是另外一个督程,那么应该设置为 infinity 以给予子树足够的时间关闭。
- Type 指定子进程是督程还是佣程。
- Modules 应该为只有一个元素的列表 [Module],其中 Module 是回调模块的名称,如果子进程是督程、gen_server或者gen_fsm。如果子进程是一个gen_event,那么 Modules 应为 dynamic 。 在升级和降级过程中发布处理器将用到这个信息,参见 发布处理 。
不知道大家能不能看明白,从 cowboy_sup.erl
%% @doc Date and time related functions. %% %% While a gen_server process runs in the background to update %% the cache of formatted dates every second, all API calls are %% local and directly read from the ETS cache table, providing %% fast time and date computations. -module(cowboy_clock). -behaviour(gen_server). -export([start_link/0, stop/0, rfc1123/0, rfc2109/1]). %% API. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server. -record(state, { universaltime = undefined :: undefined | calendar:datetime(), rfc1123 = <<>> :: binary(), tref = undefined :: undefined | timer:tref() }). -define(SERVER, ?MODULE). -define(TABLE, ?MODULE). -include_lib("eunit/include/eunit.hrl"). %% API. %% @private -spec start_link() -> {ok, pid()}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %% @private -spec stop() -> stopped. stop() -> gen_server:call(?SERVER, stop). %% @doc Return the current date and time formatted according to RFC-1123. %% %% This format is used in the <em>'Date'</em> header sent with HTTP responses. -spec rfc1123() -> binary(). rfc1123() -> ets:lookup_element(?TABLE, rfc1123, 2). %% @doc Return the current date and time formatted according to RFC-2109. %% %% This format is used in the <em>'Set-Cookie'</em> header sent with %% HTTP responses. -spec rfc2109(calendar:datetime()) -> binary(). rfc2109(LocalTime) -> {{YYYY,MM,DD},{Hour,Min,Sec}} = case calendar:local_time_to_universal_time_dst(LocalTime) of [Gmt] -> Gmt; [_,Gmt] -> Gmt; [] -> %% The localtime generated by cowboy_cookies may fall within %% the hour that is skipped by daylight savings time. If this %% is such a localtime, increment the localtime with one hour %% and try again, if this succeeds, subtracting the max_age %% from the resulting universaltime and converting to a local %% time will yield the original localtime. {Date, {Hour1, Min1, Sec1}} = LocalTime, LocalTime2 = {Date, {Hour1 + 1, Min1, Sec1}}, case calendar:local_time_to_universal_time_dst(LocalTime2) of [Gmt] -> Gmt; [_,Gmt] -> Gmt end end, Wday = calendar:day_of_the_week({YYYY,MM,DD}), DayBin = pad_int(DD), YearBin = list_to_binary(integer_to_list(YYYY)), HourBin = pad_int(Hour), MinBin = pad_int(Min), SecBin = pad_int(Sec), WeekDay = weekday(Wday), Month = month(MM), <<WeekDay/binary, ", ", DayBin/binary, " ", Month/binary, " ", YearBin/binary, " ", HourBin/binary, ":", MinBin/binary, ":", SecBin/binary, " GMT">>. %% gen_server. %% @private -spec init([]) -> {ok, #state{}}. init([]) -> ?TABLE = ets:new(?TABLE, [set, protected, named_table, {read_concurrency, true}]), T = erlang:universaltime(), B = update_rfc1123(<<>>, undefined, T), {ok, TRef} = timer:send_interval(1000, update), ets:insert(?TABLE, {rfc1123, B}), {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}. %% @private -spec handle_call(_, _, State) -> {reply, ignored, State} | {stop, normal, stopped, State}. handle_call(stop, _From, State=#state{tref=TRef}) -> {ok, cancel} = timer:cancel(TRef), {stop, normal, stopped, State}; handle_call(_Request, _From, State) -> {reply, ignored, State}. %% @private -spec handle_cast(_, State) -> {noreply, State}. handle_cast(_Msg, State) -> {noreply, State}. %% @private -spec handle_info(_, State) -> {noreply, State}. handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef}) -> T = erlang:universaltime(), B2 = update_rfc1123(B1, Prev, T), ets:insert(?TABLE, {rfc1123, B2}), {noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}}; handle_info(_Info, State) -> {noreply, State}. %% @private -spec terminate(_, _) -> ok. terminate(_Reason, _State) -> ok. %% @private -spec code_change(_, State, _) -> {ok, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}. %% Internal. -spec update_rfc1123(binary(), undefined | calendar:datetime(), calendar:datetime()) -> binary(). update_rfc1123(Bin, Now, Now) -> Bin; update_rfc1123(<< Keep:23/binary, _/bits >>, {Date, {H, M, _}}, {Date, {H, M, S}}) -> << Keep/binary, (pad_int(S))/binary, " GMT" >>; update_rfc1123(<< Keep:20/binary, _/bits >>, {Date, {H, _, _}}, {Date, {H, M, S}}) -> << Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) -> << Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>, {{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) -> Wday = calendar:day_of_the_week(Date), << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>, {{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) -> Wday = calendar:day_of_the_week(Date), << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ", (month(Mo))/binary, Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) -> Wday = calendar:day_of_the_week(Date), << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ", (month(Mo))/binary, " ", (list_to_binary(integer_to_list(Y)))/binary, " ", (pad_int(H))/binary, $:, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>. %% Following suggestion by MononcQc on #erlounge. -spec pad_int(0..59) -> binary(). pad_int(X) when X < 10 -> << $0, ($0 + X) >>; pad_int(X) -> list_to_binary(integer_to_list(X)). -spec weekday(1..7) -> <<_:24>>. weekday(1) -> <<"Mon">>; weekday(2) -> <<"Tue">>; weekday(3) -> <<"Wed">>; weekday(4) -> <<"Thu">>; weekday(5) -> <<"Fri">>; weekday(6) -> <<"Sat">>; weekday(7) -> <<"Sun">>. -spec month(1..12) -> <<_:24>>. month( 1) -> <<"Jan">>; month( 2) -> <<"Feb">>; month( 3) -> <<"Mar">>; month( 4) -> <<"Apr">>; month( 5) -> <<"May">>; month( 6) -> <<"Jun">>; month( 7) -> <<"Jul">>; month( 8) -> <<"Aug">>; month( 9) -> <<"Sep">>; month(10) -> <<"Oct">>; month(11) -> <<"Nov">>; month(12) -> <<"Dec">>. %% Tests. -ifdef(TEST). update_rfc1123_test_() -> Tests = [ {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined, {{2011, 5, 14}, {14, 25, 33}}, <<>>}, {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}, {{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>}, {<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}}, {{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>}, {<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}}, {{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>}, {<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}}, {{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>}, {<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}}, {{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>}, {<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}}, {{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>}, {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}}, {{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>} ], [{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests]. pad_int_test_() -> Tests = [ { 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>}, { 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>}, { 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>}, {12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>}, {16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>}, {20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>}, {24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>}, {28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>}, {32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>}, {36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>}, {40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>}, {44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>}, {48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>}, {52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>}, {56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>} ], [{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests]. -endif.
我们看下关于这个模块的描述:
%% @doc Date and time related functions.
%%
%% While a gen_server process runs in the background to update
%% the cache of formatted dates every second, all API calls are
%% local and directly read from the ETS cache table, providing
%% fast time and date computations.
-module(cowboy_clock).
大概意思翻译:
一个日期和时间关系的函数列表
这个是我根据表面理解的翻译:
有一个 gen_server 后台进程运行,时刻跟新格式化的日期缓存,倘若第一时间有日期的计算,所有的 api 调用,将从本地 ets 缓存表中读取。
这个是让我朋友帮我翻译的:
当一个时刻格式化日期缓存的gen_server后台进程运行时,所有API直接从Ets缓存调用读取,快速进行时间和日期计算
唉,这说明我E文实在很烂,献丑了。
好了,cowboy_clock 这个模块应该是用于计算,日期和时间相关的函数列表。
下面我们详细介绍下这个模块里的内容:
1. 首先是 module 定义,以及 一些函数导出,这个我就不介绍了,如果连这都看不懂,建议看看 Erlang程序设计 这本书,或者 Erlang 编程指南,或者官方的新手教程。
2. 下面这几行是定义记录,记录名为 state, 注意下,= 后面是 记录字段类型的描述
-record(state, {
universaltime = undefined :: undefined | calendar:datetime(),
rfc1123 = <<>> :: binary(),
tref = undefined :: undefined | timer:tref()
}).
3. 宏定义:
-define(SERVER, ?MODULE). %%服务名
-define(TABLE, ?MODULE). %%ets 表名
4. 单元测试相关,这个我打算先跳过,以后有机会,再写一篇单元测试相关的文章,还有 上一篇文章中提到的 eprof 提供函数运行时间的百分比,我以后会专门写文章介绍。
-include_lib("eunit/include/eunit.hrl").
还有下面这行往下的所有代码,咱们都跳过:
-ifdef(TEST).
5. 启动这个gen_server。注意上面这行是啥意思呢,它是文档标记语言,用户描述生成文档用的,咱们看是 start_link()这个方法返回{ok, pid()} 这个结果。
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
6. 停止这个gen_server
-spec stop() -> stopped.
stop() ->
gen_server:call(?SERVER, stop).
7. rfc1123/0方法
Return the current date and time formatted according to RFC-1123.
返回当前日期和时间,格式化为RFC-1123这样的表示方法, 大概就是这样表示日期: ddd,dd MMM yyyy,HH':'mm':'ss 'GMT'。
ets:lookup_element 这个是从 ?TABLE ets 表里取 rfc1123 这样值。
-spec rfc1123() -> binary().
rfc1123() ->
ets:lookup_element(?TABLE, rfc1123, 2).
8. rfc2109/1 方法,RFC2109规范也是一种规范。
Return the current date and time formatted according to RFC-2109.
返回当前日期和时间,格式化为 RFC-2109这样的表示方法
我们注意下面两行注释,大概意思就是 这种格式化,用于 发送 http responses 头部 'Set-Cookie' 中.
%% This format is used in the <em>'Set-Cookie'</em> header sent with
%% HTTP responses.
9. init/1 gen_server的初始化方法
-spec init([]) -> {ok, #state{}}.
init([]) ->
?TABLE = ets:new(?TABLE, [set, protected,
named_table, {read_concurrency, true}]),
T = erlang:universaltime(),
B = update_rfc1123(<<>>, undefined, T),
{ok, TRef} = timer:send_interval(1000, update),
ets:insert(?TABLE, {rfc1123, B}),
{ok, #state{universaltime=T, rfc1123=B, tref=TRef}}.
这个方法会创建,一个 ets表,创建表,需要大家百度了解下,如何创建 ets 表,在这边不详细介绍了,篇幅有限。
erlang:universaltime/0 这个方法是返回当前系统时间;
这个方法是把 当前系统时间 返回 rfc1123 的二进制格式, 大家看下面 update_rfc1123 这些方法的具体逻辑,不一一介绍了;
timer:send_interval/2 这个方法是定义一个定时器,每个1000毫秒,发送一条消息 "update" 给 self(),这里就是gen_server进程,我们看handle_info有接受update消息的处理代码;
ets:insert/2 插入ets表数据;
最后一行是返回 State。
10. handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/2 这几个方法属于 gen_server 行为管用方法,具体惨看 Erlang OTP 设计原则.
其中有这一行, {ok, cancel} = timer:cancel(TRef), 取消定时器;
跟新 ets 中 当前时间 rtf1123 格式字符串
T = erlang:universaltime(),
B2 = update_rfc1123(B1, Prev, T),
ets:insert(?TABLE, {rfc1123, B2}),
{noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}};
11. 剩下的方法 pad_int/1 这个方法是把 0 到 59 的数字转为 二进制格式;weekday/1 这个方法是返回星期的英文字母表现;month/1 这个是返回月份的英文字母表现;
到目前为止,cowboy_clock 模块中的所有方法我们都详细介绍了,再下一篇中,我会运行 cowboy的一个例子,也就是在上一篇提到的那个例子,可能你会比较疑惑,为什么 cowboy 应用程序到目前为止,只启动了一个跟日期和时间相关的模块,没有涉及别的模块呢,下回再分解,谢谢。
附上官方 timer 模块说明 http://www.erlang.org/doc/man/timer.html
本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名rhinovirus(包含链接http://www.cnblogs.com/rhinovirus/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系。