Cowboy 源碼分析(二)


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

  Procs = [{cowboy_clock, {cowboy_clock, start_link, []},
  permanent, 5000, worker, [cowboy_clock]}],
  {ok, {{one_for_one, 10, 10}, Procs}}.
  我們現在應該能夠理解這三行代碼的意思,其實就是在 cowboy_sup 監控進程樹下,有個模塊cowboy_clock,當應用程序啟動時,這個監控進程樹就會啟動,然后在這棵樹下,啟動一個模塊,就是 cowboy_clock
  
  cowboy_clock 是 gen_server行為的一個模塊,也就是客戶端-服務器端(C/S)模型。完整代碼如下:
%% @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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM