cowboy_clock.erl 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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. %% While a gen_server process runs in the background to update
  15. %% the cache of formatted dates every second, all API calls are
  16. %% local and directly read from the ETS cache table, providing
  17. %% fast time and date computations.
  18. -module(cowboy_clock).
  19. -behaviour(gen_server).
  20. %% API.
  21. -export([start_link/0]).
  22. -export([stop/0]).
  23. -export([rfc1123/0]).
  24. -export([rfc1123/1]).
  25. %% gen_server.
  26. -export([init/1]).
  27. -export([handle_call/3]).
  28. -export([handle_cast/2]).
  29. -export([handle_info/2]).
  30. -export([terminate/2]).
  31. -export([code_change/3]).
  32. -record(state, {
  33. universaltime = undefined :: undefined | calendar:datetime(),
  34. rfc1123 = <<>> :: binary(),
  35. tref = undefined :: undefined | reference()
  36. }).
  37. %% API.
  38. -spec start_link() -> {ok, pid()}.
  39. start_link() ->
  40. gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  41. -spec stop() -> stopped.
  42. stop() ->
  43. gen_server:call(?MODULE, stop).
  44. %% When the ets table doesn't exist, either because of a bug
  45. %% or because Cowboy is being restarted, we perform in a
  46. %% slightly degraded state and build a new timestamp for
  47. %% every request.
  48. -spec rfc1123() -> binary().
  49. rfc1123() ->
  50. try
  51. ets:lookup_element(?MODULE, rfc1123, 2)
  52. catch error:badarg ->
  53. rfc1123(erlang:universaltime())
  54. end.
  55. -spec rfc1123(calendar:datetime()) -> binary().
  56. rfc1123(DateTime) ->
  57. update_rfc1123(<<>>, undefined, DateTime).
  58. %% gen_server.
  59. -spec init([]) -> {ok, #state{}}.
  60. init([]) ->
  61. ?MODULE = ets:new(?MODULE, [set, protected,
  62. named_table, {read_concurrency, true}]),
  63. T = erlang:universaltime(),
  64. B = update_rfc1123(<<>>, undefined, T),
  65. TRef = erlang:send_after(1000, self(), update),
  66. ets:insert(?MODULE, {rfc1123, B}),
  67. {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}.
  68. -type from() :: {pid(), term()}.
  69. -spec handle_call
  70. (stop, from(), State) -> {stop, normal, stopped, State}
  71. when State::#state{}.
  72. handle_call(stop, _From, State) ->
  73. {stop, normal, stopped, State};
  74. handle_call(_Request, _From, State) ->
  75. {reply, ignored, State}.
  76. -spec handle_cast(_, State) -> {noreply, State} when State::#state{}.
  77. handle_cast(_Msg, State) ->
  78. {noreply, State}.
  79. -spec handle_info(any(), State) -> {noreply, State} when State::#state{}.
  80. handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef0}) ->
  81. %% Cancel the timer in case an external process sent an update message.
  82. _ = erlang:cancel_timer(TRef0),
  83. T = erlang:universaltime(),
  84. B2 = update_rfc1123(B1, Prev, T),
  85. ets:insert(?MODULE, {rfc1123, B2}),
  86. TRef = erlang:send_after(1000, self(), update),
  87. {noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}};
  88. handle_info(_Info, State) ->
  89. {noreply, State}.
  90. -spec terminate(_, _) -> ok.
  91. terminate(_Reason, _State) ->
  92. ok.
  93. -spec code_change(_, State, _) -> {ok, State} when State::#state{}.
  94. code_change(_OldVsn, State, _Extra) ->
  95. {ok, State}.
  96. %% Internal.
  97. -spec update_rfc1123(binary(), undefined | calendar:datetime(),
  98. calendar:datetime()) -> binary().
  99. update_rfc1123(Bin, Now, Now) ->
  100. Bin;
  101. update_rfc1123(<< Keep:23/binary, _/bits >>,
  102. {Date, {H, M, _}}, {Date, {H, M, S}}) ->
  103. << Keep/binary, (pad_int(S))/binary, " GMT" >>;
  104. update_rfc1123(<< Keep:20/binary, _/bits >>,
  105. {Date, {H, _, _}}, {Date, {H, M, S}}) ->
  106. << Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>;
  107. update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) ->
  108. << Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary,
  109. $:, (pad_int(S))/binary, " GMT" >>;
  110. update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>,
  111. {{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
  112. Wday = calendar:day_of_the_week(Date),
  113. << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary,
  114. (pad_int(H))/binary, $:, (pad_int(M))/binary,
  115. $:, (pad_int(S))/binary, " GMT" >>;
  116. update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>,
  117. {{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
  118. Wday = calendar:day_of_the_week(Date),
  119. << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
  120. (month(Mo))/binary, Keep/binary,
  121. (pad_int(H))/binary, $:, (pad_int(M))/binary,
  122. $:, (pad_int(S))/binary, " GMT" >>;
  123. update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) ->
  124. Wday = calendar:day_of_the_week(Date),
  125. << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
  126. (month(Mo))/binary, " ", (integer_to_binary(Y))/binary,
  127. " ", (pad_int(H))/binary, $:, (pad_int(M))/binary,
  128. $:, (pad_int(S))/binary, " GMT" >>.
  129. %% Following suggestion by MononcQc on #erlounge.
  130. -spec pad_int(0..59) -> binary().
  131. pad_int(X) when X < 10 ->
  132. << $0, ($0 + X) >>;
  133. pad_int(X) ->
  134. integer_to_binary(X).
  135. -spec weekday(1..7) -> <<_:24>>.
  136. weekday(1) -> <<"Mon">>;
  137. weekday(2) -> <<"Tue">>;
  138. weekday(3) -> <<"Wed">>;
  139. weekday(4) -> <<"Thu">>;
  140. weekday(5) -> <<"Fri">>;
  141. weekday(6) -> <<"Sat">>;
  142. weekday(7) -> <<"Sun">>.
  143. -spec month(1..12) -> <<_:24>>.
  144. month( 1) -> <<"Jan">>;
  145. month( 2) -> <<"Feb">>;
  146. month( 3) -> <<"Mar">>;
  147. month( 4) -> <<"Apr">>;
  148. month( 5) -> <<"May">>;
  149. month( 6) -> <<"Jun">>;
  150. month( 7) -> <<"Jul">>;
  151. month( 8) -> <<"Aug">>;
  152. month( 9) -> <<"Sep">>;
  153. month(10) -> <<"Oct">>;
  154. month(11) -> <<"Nov">>;
  155. month(12) -> <<"Dec">>.
  156. %% Tests.
  157. -ifdef(TEST).
  158. update_rfc1123_test_() ->
  159. Tests = [
  160. {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined,
  161. {{2011, 5, 14}, {14, 25, 33}}, <<>>},
  162. {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
  163. {{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
  164. {<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
  165. {{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
  166. {<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}},
  167. {{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>},
  168. {<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}},
  169. {{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>},
  170. {<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}},
  171. {{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>},
  172. {<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
  173. {{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>},
  174. {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
  175. {{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>}
  176. ],
  177. [{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests].
  178. pad_int_test_() ->
  179. Tests = [
  180. { 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>},
  181. { 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>},
  182. { 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>},
  183. {12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>},
  184. {16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>},
  185. {20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>},
  186. {24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>},
  187. {28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>},
  188. {32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>},
  189. {36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>},
  190. {40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>},
  191. {44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>},
  192. {48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>},
  193. {52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>},
  194. {56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>}
  195. ],
  196. [{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests].
  197. -endif.