cowboy_clock.erl (8173B)
1 %% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu> 2 %% 3 %% Permission to use, copy, modify, and/or distribute this software for any 4 %% purpose with or without fee is hereby granted, provided that the above 5 %% copyright notice and this permission notice appear in all copies. 6 %% 7 %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15 %% While a gen_server process runs in the background to update 16 %% the cache of formatted dates every second, all API calls are 17 %% local and directly read from the ETS cache table, providing 18 %% fast time and date computations. 19 -module(cowboy_clock). 20 -behaviour(gen_server). 21 22 %% API. 23 -export([start_link/0]). 24 -export([stop/0]). 25 -export([rfc1123/0]). 26 -export([rfc1123/1]). 27 28 %% gen_server. 29 -export([init/1]). 30 -export([handle_call/3]). 31 -export([handle_cast/2]). 32 -export([handle_info/2]). 33 -export([terminate/2]). 34 -export([code_change/3]). 35 36 -record(state, { 37 universaltime = undefined :: undefined | calendar:datetime(), 38 rfc1123 = <<>> :: binary(), 39 tref = undefined :: undefined | reference() 40 }). 41 42 %% API. 43 44 -spec start_link() -> {ok, pid()}. 45 start_link() -> 46 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 47 48 -spec stop() -> stopped. 49 stop() -> 50 gen_server:call(?MODULE, stop). 51 52 %% When the ets table doesn't exist, either because of a bug 53 %% or because Cowboy is being restarted, we perform in a 54 %% slightly degraded state and build a new timestamp for 55 %% every request. 56 -spec rfc1123() -> binary(). 57 rfc1123() -> 58 try 59 ets:lookup_element(?MODULE, rfc1123, 2) 60 catch error:badarg -> 61 rfc1123(erlang:universaltime()) 62 end. 63 64 -spec rfc1123(calendar:datetime()) -> binary(). 65 rfc1123(DateTime) -> 66 update_rfc1123(<<>>, undefined, DateTime). 67 68 %% gen_server. 69 70 -spec init([]) -> {ok, #state{}}. 71 init([]) -> 72 ?MODULE = ets:new(?MODULE, [set, protected, 73 named_table, {read_concurrency, true}]), 74 T = erlang:universaltime(), 75 B = update_rfc1123(<<>>, undefined, T), 76 TRef = erlang:send_after(1000, self(), update), 77 ets:insert(?MODULE, {rfc1123, B}), 78 {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}. 79 80 -type from() :: {pid(), term()}. 81 -spec handle_call 82 (stop, from(), State) -> {stop, normal, stopped, State} 83 when State::#state{}. 84 handle_call(stop, _From, State) -> 85 {stop, normal, stopped, State}; 86 handle_call(_Request, _From, State) -> 87 {reply, ignored, State}. 88 89 -spec handle_cast(_, State) -> {noreply, State} when State::#state{}. 90 handle_cast(_Msg, State) -> 91 {noreply, State}. 92 93 -spec handle_info(any(), State) -> {noreply, State} when State::#state{}. 94 handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef0}) -> 95 %% Cancel the timer in case an external process sent an update message. 96 _ = erlang:cancel_timer(TRef0), 97 T = erlang:universaltime(), 98 B2 = update_rfc1123(B1, Prev, T), 99 ets:insert(?MODULE, {rfc1123, B2}), 100 TRef = erlang:send_after(1000, self(), update), 101 {noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}}; 102 handle_info(_Info, State) -> 103 {noreply, State}. 104 105 -spec terminate(_, _) -> ok. 106 terminate(_Reason, _State) -> 107 ok. 108 109 -spec code_change(_, State, _) -> {ok, State} when State::#state{}. 110 code_change(_OldVsn, State, _Extra) -> 111 {ok, State}. 112 113 %% Internal. 114 115 -spec update_rfc1123(binary(), undefined | calendar:datetime(), 116 calendar:datetime()) -> binary(). 117 update_rfc1123(Bin, Now, Now) -> 118 Bin; 119 update_rfc1123(<< Keep:23/binary, _/bits >>, 120 {Date, {H, M, _}}, {Date, {H, M, S}}) -> 121 << Keep/binary, (pad_int(S))/binary, " GMT" >>; 122 update_rfc1123(<< Keep:20/binary, _/bits >>, 123 {Date, {H, _, _}}, {Date, {H, M, S}}) -> 124 << Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; 125 update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) -> 126 << Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary, 127 $:, (pad_int(S))/binary, " GMT" >>; 128 update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>, 129 {{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) -> 130 Wday = calendar:day_of_the_week(Date), 131 << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary, 132 (pad_int(H))/binary, $:, (pad_int(M))/binary, 133 $:, (pad_int(S))/binary, " GMT" >>; 134 update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>, 135 {{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) -> 136 Wday = calendar:day_of_the_week(Date), 137 << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ", 138 (month(Mo))/binary, Keep/binary, 139 (pad_int(H))/binary, $:, (pad_int(M))/binary, 140 $:, (pad_int(S))/binary, " GMT" >>; 141 update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) -> 142 Wday = calendar:day_of_the_week(Date), 143 << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ", 144 (month(Mo))/binary, " ", (integer_to_binary(Y))/binary, 145 " ", (pad_int(H))/binary, $:, (pad_int(M))/binary, 146 $:, (pad_int(S))/binary, " GMT" >>. 147 148 %% Following suggestion by MononcQc on #erlounge. 149 -spec pad_int(0..59) -> binary(). 150 pad_int(X) when X < 10 -> 151 << $0, ($0 + X) >>; 152 pad_int(X) -> 153 integer_to_binary(X). 154 155 -spec weekday(1..7) -> <<_:24>>. 156 weekday(1) -> <<"Mon">>; 157 weekday(2) -> <<"Tue">>; 158 weekday(3) -> <<"Wed">>; 159 weekday(4) -> <<"Thu">>; 160 weekday(5) -> <<"Fri">>; 161 weekday(6) -> <<"Sat">>; 162 weekday(7) -> <<"Sun">>. 163 164 -spec month(1..12) -> <<_:24>>. 165 month( 1) -> <<"Jan">>; 166 month( 2) -> <<"Feb">>; 167 month( 3) -> <<"Mar">>; 168 month( 4) -> <<"Apr">>; 169 month( 5) -> <<"May">>; 170 month( 6) -> <<"Jun">>; 171 month( 7) -> <<"Jul">>; 172 month( 8) -> <<"Aug">>; 173 month( 9) -> <<"Sep">>; 174 month(10) -> <<"Oct">>; 175 month(11) -> <<"Nov">>; 176 month(12) -> <<"Dec">>. 177 178 %% Tests. 179 180 -ifdef(TEST). 181 update_rfc1123_test_() -> 182 Tests = [ 183 {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined, 184 {{2011, 5, 14}, {14, 25, 33}}, <<>>}, 185 {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}, 186 {{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>}, 187 {<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}}, 188 {{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>}, 189 {<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}}, 190 {{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>}, 191 {<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}}, 192 {{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>}, 193 {<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}}, 194 {{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>}, 195 {<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}}, 196 {{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>}, 197 {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}}, 198 {{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>} 199 ], 200 [{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests]. 201 202 pad_int_test_() -> 203 Tests = [ 204 { 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>}, 205 { 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>}, 206 { 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>}, 207 {12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>}, 208 {16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>}, 209 {20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>}, 210 {24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>}, 211 {28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>}, 212 {32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>}, 213 {36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>}, 214 {40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>}, 215 {44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>}, 216 {48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>}, 217 {52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>}, 218 {56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>} 219 ], 220 [{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests]. 221 -endif.