Permalink
Browse files

Added webmachine_router so applications can add/remove routes at runtime

  • Loading branch information...
1 parent c6e0231 commit caf4e7eb6093e7473443d12444f1c1f3193a2549 Kevin Smith committed Mar 11, 2010
Showing with 199 additions and 17 deletions.
  1. +1 −0 ebin/webmachine.app
  2. +6 −3 priv/templates/src/wmskel_sup.erl
  3. +189 −0 src/webmachine_router.erl
  4. +3 −14 src/wmtrace_resource.erl
View
@@ -12,6 +12,7 @@
webmachine_perf_logger,
webmachine_resource,
webmachine_request,
+ webmachine_router,
webmachine_sup,
webmachine_mochiweb,
webmachine_multipart,
@@ -51,7 +51,10 @@ init([]) ->
{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} }.
View
@@ -0,0 +1,189 @@
+%% @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).
View
@@ -41,23 +41,12 @@
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
@@ -294,7 +283,7 @@ encode_request(ReqData) when is_record(ReqData, wm_reqdata) ->
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))},

0 comments on commit caf4e7e

Please sign in to comment.