Anonymous avatar Anonymous committed e9f2a29

Added webmachine_router so applications can add/remove routes at runtime

Comments (0)

Files changed (4)

ebin/webmachine.app

     webmachine_perf_logger,
     webmachine_resource,
     webmachine_request,
+    webmachine_router,
     webmachine_sup,
     webmachine_mochiweb,
     webmachine_multipart,

priv/templates/src/wmskel_sup.erl

                  {log_dir, "priv/log"},
 		 {dispatch, Dispatch}],
     Web = {webmachine_mochiweb,
-	   {webmachine_mochiweb, start, [WebConfig]},
-	   permanent, 5000, worker, dynamic},
-    Processes = [Web],
+           {webmachine_mochiweb, start, [WebConfig]},
+           permanent, 5000, worker, dynamic},
+    Router = {webmachine_router,
+              {webmachine_router, start_link, []},
+              permanent, 5000, worker, dynamic},
+    Processes = [Web, Router],
     {ok, { {one_for_one, 10, 10}, Processes} }.

src/webmachine_router.erl

+%% @author Kevin A. Smith <ksmith@basho.com>
+%% @copyright 2007-2010 Basho Technologies
+%%
+%%    Licensed under the Apache License, Version 2.0 (the "License");
+%%    you may not use this file except in compliance with the License.
+%%    You may obtain a copy of the License at
+%%
+%%        http://www.apache.org/licenses/LICENSE-2.0
+%%
+%%    Unless required by applicable law or agreed to in writing, software
+%%    distributed under the License is distributed on an "AS IS" BASIS,
+%%    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%%    See the License for the specific language governing permissions and
+%%    limitations under the License.
+
+%% @doc Module to add and remove dynamic routes to webmachine's routing
+%%      table. Dynamic routes are not persistent between executions of
+%%      a webmachine application. They will need to be added to the
+%%      the table each time webmachine restarts.
+-module(webmachine_router).
+
+-include_lib("eunit/include/eunit.hrl").
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0,
+         add_route/1,
+         remove_route/1,
+         remove_resource/1]).
+
+%% gen_server callbacks
+-export([init/1,
+         handle_call/3,
+         handle_cast/2,
+         handle_info/2,
+         terminate/2,
+         code_change/3]).
+
+%% @type hostmatchterm() = {hostmatch(), [pathmatchterm()]}.
+% The dispatch configuration contains a list of these terms, and the
+% first one whose host and one pathmatchterm match is used.
+
+%% @type pathmatchterm() = {[pathterm()], matchmod(), matchopts()}.
+% The dispatch configuration contains a list of these terms, and the
+% first one whose list of pathterms matches the input path is used.
+
+%% @type pathterm() = '*' | string() | atom().
+% A list of pathterms is matched against a '/'-separated input path.
+% The '*' pathterm matches all remaining tokens.
+% A string pathterm will match a token of exactly the same string.
+% Any atom pathterm other than '*' will match any token and will
+% create a binding in the result if a complete match occurs.
+
+%% @type matchmod() = atom().
+% This atom, if present in a successful matchterm, will appear in
+% the resulting dispterm.  In Webmachine this is used to name the
+% resource module that will handle the matching request.
+
+%% @type matchopts() = [term()].
+% This term, if present in a successful matchterm, will appear in
+% the resulting dispterm.  In Webmachine this is used to provide
+% arguments to the resource module handling the matching request.
+
+-define(SERVER, ?MODULE).
+
+%% @spec add_route(hostmatchterm() | pathmatchterm()) -> ok
+%% @doc Adds a route to webmachine's route table. The route should
+%%      be the format documented here:
+%% http://bitbucket.org/justin/webmachine/wiki/DispatchConfiguration
+add_route(Route) ->
+    gen_server:call(?SERVER, {add_route, Route}).
+
+%% @spec remove_route(hostmatchterm() | pathmatchterm()) -> ok
+%% @doc Removes a route from webamchine's route table. The route
+%%      route must be properly formatted
+%% @see add_route/2
+remove_route(Route) ->
+    gen_server:call(?SERVER, {remove_route, Route}).
+
+%% @spec remove_resource(atom()) -> ok
+%% @doc Removes all routes for a specific resource module.
+remove_resource(Resource) when is_atom(Resource) ->
+    gen_server:call(?SERVER, {remove_resource, Resource}).
+
+%% @spec start_link() -> {ok, pid()} | {error, any()}
+%% @doc Starts the webmachine_router gen_server.
+start_link() ->
+  gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+
+%% @private
+init([]) ->
+  {ok, []}.
+
+%% @private
+handle_call(get_routes, _From, State) ->
+    {reply, get_dispatch_list(), State};
+
+handle_call({remove_resource, Resource}, _From, State) ->
+    DL = [D || D <- get_dispatch_list(),
+               filter_by_resource(D, Resource)],
+    {reply, set_dispatch_list(DL), State};
+
+handle_call({remove_route, Route}, _From, State) ->
+    DL = [D || D <- get_dispatch_list(),
+               D /= Route],
+    {reply, set_dispatch_list(DL), State};
+
+handle_call({add_route, Route}, _From, State) ->
+    DL = [Route|[D || D <- get_dispatch_list(),
+                      D /= Route]],
+    {reply, set_dispatch_list(DL), State};
+
+handle_call(_Request, _From, State) ->
+  {reply, ignore, State}.
+
+%% @private
+handle_cast(_Msg, State) ->
+  {noreply, State}.
+
+%% @private
+handle_info(_Info, State) ->
+  {noreply, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+  ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+  {ok, State}.
+
+%% Internal functions
+filter_by_resource({_, {_, Resource, _}}, Resource) ->
+    false;
+filter_by_resource({_, {_, _, _}}, _Resource) ->
+    true;
+filter_by_resource({_, Resource, _}, Resource) ->
+    false;
+filter_by_resource({_, _, _}, _Resource) ->
+    true.
+
+get_dispatch_list() ->
+    {ok, Dispatch} = application:get_env(webmachine, dispatch_list),
+    Dispatch.
+
+set_dispatch_list(DispatchList) ->
+    ok = application:set_env(webmachine, dispatch_list, DispatchList),
+    ok.
+
+%% For unit tests only
+start() ->
+    gen_server:start({local, ?SERVER}, ?MODULE, [], []).
+
+get_routes() ->
+    gen_server:call(?SERVER, get_routes).
+
+%% Tests
+add_remove_route_test() ->
+    application:set_env(webmachine, dispatch_list, []),
+    {ok, Pid} = start(),
+    PathSpec = {["foo"], foo, []},
+    webmachine_router:add_route(PathSpec),
+    [PathSpec] = get_routes(),
+    webmachine_router:remove_route(PathSpec),
+    [] = get_routes(),
+    exit(Pid, kill).
+
+add_remove_resource_test() ->
+    application:set_env(webmachine, dispatch_list, []),
+    {ok, Pid} = start(),
+    PathSpec1 = {["foo"], foo, []},
+    PathSpec2 = {["bar"], foo, []},
+    PathSpec3 = {["baz"], bar, []},
+    webmachine_router:add_route(PathSpec1),
+    webmachine_router:add_route(PathSpec2),
+    webmachine_router:add_route(PathSpec3),
+    webmachine_router:remove_resource(foo),
+    [PathSpec3] = get_routes(),
+    exit(Pid, kill).
+
+no_dupe_path_test() ->
+    application:set_env(webmachine, dispatch_list, []),
+    {ok, Pid} = start(),
+    PathSpec = {["foo"], foo, []},
+    webmachine_router:add_route(PathSpec),
+    webmachine_router:add_route(PathSpec),
+    [PathSpec] = get_routes(),
+    exit(Pid, kill).

src/wmtrace_resource.erl

 add_dispatch_rule(BasePath, TracePath) when is_list(BasePath),
                                             is_list(TracePath) ->
     Parts = string:tokens(BasePath, "/"),
-    set_dispatch_list(
-      [{Parts++['*'], ?MODULE, [{trace_dir, TracePath}]}
-       |get_dispatch_list()]).
+    webmachine_router:add_pathspec({Parts ++ ['*'], ?MODULE, [{trace_dir, TracePath}]}).
 
 %% @spec remove_dispatch_rules() -> ok
 %% @doc Remove all dispatch rules pointing to wmtrace_resource.
 remove_dispatch_rules() ->
-    set_dispatch_list(
-      [ D || D={_,M,_} <- get_dispatch_list(),
-             M /= ?MODULE ]).
-
-get_dispatch_list() ->
-    {ok, Dispatch} = application:get_env(webmachine, dispatch_list),
-    Dispatch.
-
-set_dispatch_list(NewList) when is_list(NewList) ->
-    application:set_env(webmachine, dispatch_list, NewList).
+    webmachine_router:remove_resource(?MODULE).
 
 %%
 %% Resource
                                atom_to_list(Body);
                            Body -> lists:flatten(io_lib:format("~s", [Body]))
                        end}]}.
-    
+
 encode_response(ReqData) ->
     {struct, [{"code", integer_to_list(
                          wrq:response_code(ReqData))},
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.