Anonymous avatar Anonymous committed a8b6066

add dispatch_watcher, a gen_server that automatically reloads the dispatch configuration file when it changes

Comments (0)

Files changed (3)

 
 Issuing a POST to formjson_resource will assume that the body of the
 request is application/x-www-form-urlencoded data, re-ecode it as
-JSON, and return that JSON in the response body.
+JSON, and return that JSON in the response body.
+
+Dispatch Reloader
+---
+
+dispatch_watcher.erl defines a gen_server that can be started in the
+background, and will watch dispatch.conf for changes, reloading it
+when necessary.
+
+By default, it checks "priv/dispatch.conf" for updates once a second.
+This behavior can be changed by passing the configuration parameters:
+
+  {dispatch, Path}: where Path is a string describing the path of an
+                    alternate dispatch file
+  {interval, Time}: where Time is an integer number of milliseconds
+                    describing how long to wait between checks for
+                    updates to the dispatch file
+
+wmexamples starts dispatch_watcher by including a supervisor child
+specification in the return value of wmexamples_sup:init/1.  It could
+be started instead by appending "-s dispatch_watcher" to the final
+command of start-dev.sh, to get a dev-vs-production kind of
+configuration.

src/dispatch_watcher.erl

+%% @author Bryan Fink
+%% @doc dispatch_watcher monitors a file that is expected to contain
+%%      a Webmachine dispatcher configuration.  The file will be
+%%      reloaded when its mtime changes.
+
+-module(dispatch_watcher).
+-include_lib("kernel/include/file.hrl").
+
+-behaviour(gen_server).
+-export([start/0, start/1,
+         start_link/0, start_link/1,
+         stop/0,
+         force/0,
+         init/1,
+         handle_call/3,
+         handle_cast/2,
+         handle_info/2,
+         terminate/2,
+         code_change/3]).
+
+-record(state, {last, dispatch, interval}).
+
+-define(DEFAULT_DISPATCH, "priv/dispatch.conf").
+-define(DEFAULT_INTERVAL, 1000).
+
+%% External API
+
+%% @spec start() -> Result
+%% @doc Start the watcher with the default dispatch path
+%%      and monitor interval.
+start() ->
+    start([]).
+
+%% @spec start(Options) -> Result
+%% @type Options = [Option]
+%% @type Option = {dispatch, Path}|{interval, integer()}
+%% @type Path = string()
+%% @doc Start the watching, monitoring the file given by the
+%%      'dispatch' path option at the interval given by the
+%%      'interval' option, in milliseconds (or the default path
+%%      and interval if the option is not specified).
+start(Props) ->
+    gen_server:start({local, ?MODULE}, ?MODULE, Props, []).
+
+%% @spec start_link() -> Result
+%% @see start/0
+start_link() ->
+    start_link([]).
+
+%% @spec start_link(Options) -> Result
+%% @see start/1
+start_link(Props) ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, Props, []).
+
+stop() ->
+    gen_server:call(?MODULE, stop).
+
+%% @spec force() -> ok|Error
+%% @doc Force an immediate attempt to reload the dispatch file.
+force() ->
+    gen_server:call(?MODULE, reload_now).
+
+%% gen_server callbacks
+
+init(Props) ->
+    Dispatch = proplists:get_value(dispatch, Props,
+                                   ?DEFAULT_DISPATCH),
+    Interval = proplists:get_value(interval, Props,
+                                  ?DEFAULT_INTERVAL),
+    {ok, #state{last=stamp(),
+                dispatch=Dispatch,
+                interval=Interval},
+     Interval}.
+
+handle_call(reload_now, _From, State) ->
+    {Outcome, NewState} = attempt_reload(State, true),
+    {reply, Outcome, NewState, NewState#state.interval};
+handle_call(stop, _From, State) ->
+    {stop, shutdown, stopped, State};
+handle_call(_Req, _From, State) ->
+    {reply, {error, badrequest}, State, State#state.interval}.
+
+handle_cast(_Req, State) ->
+    {noreply, State, State#state.interval}.
+
+handle_info(timeout, State) ->
+    {_Outcome, NewState} = attempt_reload(State, false),
+    {noreply, NewState, NewState#state.interval};
+handle_info(_Info, State) ->
+    {noreply, State, State#state.interval}.
+
+terminate(_Reason, _State) ->
+    ok.
+
+code_change(_Vsn, State, _Extra) ->
+    {ok, State}.
+
+%% Internal API
+
+attempt_reload(State, Force) ->
+    Now = stamp(),
+    Changed = case file:read_file_info(State#state.dispatch) of
+                  {ok, FileInfo} ->
+                      FileInfo#file_info.mtime >= State#state.last
+                          andalso FileInfo#file_info.mtime < Now;
+                  {error, Reason} ->
+                      {error, Reason}
+              end,
+    case {Changed, Force} of
+        {true, _}      -> attempt_reload2(State#state{last=Now});
+        {false, true}  -> attempt_reload2(State#state{last=Now});
+%%% do not update timestamp if reload not attempted
+        {false, false} -> {nothing_to_do, State};
+        {Error, _}     ->
+            io:format("Dispatch reload aborted: ~p~n", [Error]),
+            {Error, State}
+    end.
+
+attempt_reload2(State) ->
+    case file:consult(State#state.dispatch) of
+        {ok, NewDispatch} ->
+            application:set_env(webmachine, dispatch_list, NewDispatch),
+            io:format("Reloaded dispatch from ~p~n", [State#state.dispatch]),
+            {ok, State};
+        Error ->
+            io:format("Dispatch reload failed: ~p~n", [Error]),
+            {Error, State}
+    end.
+
+stamp() ->
+    erlang:localtime().

src/wmexamples_sup.erl

     Web = {webmachine_mochiweb,
 	   {webmachine_mochiweb, start, [WebConfig]},
 	   permanent, 5000, worker, dynamic},
-    Processes = [Web],
+    DispLoad = {dispatch_watcher,
+                {dispatch_watcher, start, []},
+                %% above is same as
+                %% {dispatch_watcher, start,
+                %%  [[{dispatch, "priv/dispatch.conf"},
+                %%    {interval, 1000}]]},
+                permanent, 5000, worker, dynamic},
+    Processes = [Web, DispLoad],
     {ok, {{one_for_one, 10, 10}, Processes}}.
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.