大家好,在上一篇文章中,我們簡單介紹了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