From af5164a191a650889bcd5b4c45bafc9e36a5ff4d Mon Sep 17 00:00:00 2001 From: justin Date: Thu, 7 May 2009 12:02:23 -0400 Subject: [PATCH] clean up dispatcher api --- src/webmachine_decision_core.erl | 4 +- src/webmachine_dispatcher.erl | 210 +++++++++++-------------------- src/webmachine_mochiweb.erl | 9 +- src/webmachine_sup.erl | 13 +- 4 files changed, 83 insertions(+), 153 deletions(-) diff --git a/src/webmachine_decision_core.erl b/src/webmachine_decision_core.erl index 1f8eee51..442d76ea 100644 --- a/src/webmachine_decision_core.erl +++ b/src/webmachine_decision_core.erl @@ -52,7 +52,7 @@ respond(Code) -> EndTime = now(), case Code of 404 -> - ErrorHandler = webmachine_dispatcher:get_error_handler(), + {ok, ErrorHandler} = application:get_env(webmachine, error_handler), Reason = {none, none, []}, ErrorHTML = ErrorHandler:render_error(Code, get(req), Reason), wrcall({set_resp_body, ErrorHTML}); @@ -89,7 +89,7 @@ respond(Code, Headers) -> respond(Code). error_response(Code, Reason) -> - ErrorHandler = webmachine_dispatcher:get_error_handler(), + {ok, ErrorHandler} = application:get_env(webmachine, error_handler), ErrorHTML = ErrorHandler:render_error(Code, get(req), Reason), wrcall({set_resp_body, ErrorHTML}), respond(Code). diff --git a/src/webmachine_dispatcher.erl b/src/webmachine_dispatcher.erl index c6f10c82..9749dd00 100644 --- a/src/webmachine_dispatcher.erl +++ b/src/webmachine_dispatcher.erl @@ -1,6 +1,6 @@ %% @author Robert Ahrens -%% @copyright 2008 Basho Technologies -%% @copyright 2007-2008 Basho Technologies +%% @author Justin Sheehy +%% @copyright 2007-2009 Basho Technologies %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -14,117 +14,82 @@ %% See the License for the specific language governing permissions and %% limitations under the License. -%% @doc gen_server for dispatching page requests from webmachine. +%% @doc Module for URL-dispatch by pattern matching. -module(webmachine_dispatcher). -author('Robert Ahrens '). --behaviour(gen_server). +-author('Justin Sheehy '). + +-export([dispatch/2]). -define(SEPARATOR, $\/). -define(MATCH_ALL, '*'). -%% API --export([start_link/0, start_link/1, stop/0, dispatch/1, - set_dispatch_list/1, get_dispatch_list/0]). --export([set_error_handler/1, get_error_handler/0]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --record(state, {dispatchlist=[], error_handler}). - -%%==================================================================== -%% API -%%==================================================================== -%% @spec start_link() -> {ok,Pid} | ignore | {error,Error} -%% @doc Starts the dispatch server -start_link() -> - start_link([]). -start_link(DispatchList) when is_list(DispatchList) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [DispatchList], []). - -stop() -> - gen_server:cast(?MODULE, stop). - -dispatch(Req) -> - gen_server:call(?MODULE, {dispatch, Req}). - -set_dispatch_list(List) when is_list(List) -> - gen_server:cast(?MODULE, {set_dispatch_list, List}). - -get_dispatch_list() -> - gen_server:call(?MODULE, get_dispatch_list). - -set_error_handler(ErrorHandlerMod) when is_atom(ErrorHandlerMod) -> - gen_server:cast(?MODULE, {set_error_handler, ErrorHandlerMod}). - -get_error_handler() -> - gen_server:call(?MODULE, get_error_handler). - - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%% @spec init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% @doc Initiates the server -init([DispatchList]) -> - {ok, #state{dispatchlist=DispatchList}}. - -%% @spec %% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -handle_call({dispatch, Req}, From, State) -> - spawn(fun() -> binding_dispatch(State#state.dispatchlist, Req, From) end), - {noreply, State}; -handle_call(get_error_handler, _From, State) -> - {reply, State#state.error_handler, State}; -handle_call(get_dispatch_list, _From, State) -> - {reply, State#state.dispatchlist, State}. - -%% @spec handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% @doc Handling cast messages -handle_cast(stop, State) -> - {stop, normal, State}; -handle_cast({set_error_handler, ErrorHandlerMod}, State) -> - {noreply, State#state{error_handler=ErrorHandlerMod}}; -handle_cast({set_dispatch_list, List}, State) when is_list(List) -> - {noreply, State#state{dispatchlist=List}}. - -%% @spec handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% @doc Handling all non call/cast messages -handle_info(_Info, State) -> - {noreply, State}. - -%% @spec terminate(Reason, State) -> void() -%% @doc This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -terminate(_Reason, _State) -> - ok. - -%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} -%% @doc Convert process state when code is changed - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- +%% @spec dispatch(Path::string(), DispatchList::[matchterm()]) -> +%% dispterm() | dispfail() +%% @doc Interface for URL dispatching. +%% See also http://bitbucket.org/justin/webmachine/wiki/DispatchConfiguration +dispatch(PathAsString, DispatchList) -> + Path = string:tokens(PathAsString, [?SEPARATOR]), + % URIs that end with a trailing slash are implicitly one token + % "deeper" than we otherwise might think as we are "inside" + % a directory named by the last token. + ExtraDepth = case lists:last(PathAsString) == ?SEPARATOR of + true -> 1; + _ -> 0 + end, + try_binding(DispatchList, Path, ExtraDepth). + +%% @type matchterm() = {[pathterm()], matchmod(), matchopts()}. +% The dispatch configuration is 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. + +%% @type dispterm() = {matchmod(), matchopts(), pathtokens(), +%% bindings(), approot(), stringpath()}. + +%% @type pathtokens() = [pathtoken()]. +% This is the list of tokens matched by a trailing '*' pathterm. + +%% @type pathtoken() = string(). + +%% @type bindings() = [{bindingterm(),pathtoken()}]. +% This is a proplist of bindings indicated by atom terms in the +% matching spec, bound to the matching tokens in the request path. + +%% @type approot() = string(). + +%% @type stringpath() = string(). +% This is the path portion matched by a trailing '*' pathterm. + +%% @type dispfail() = {no_dispatch_match, pathtokens()}. + +try_binding([], PathTokens, _) -> + {no_dispatch_match, PathTokens}; +try_binding([{PathSchema, Mod, Props}|Rest], PathTokens, ExtraDepth) -> + case bind_path(PathSchema, PathTokens, [], 0) of + {ok, Remainder, Bindings, Depth} -> + {Mod, Props, Remainder, Bindings, + calculate_app_root(Depth + ExtraDepth), reconstitute(Remainder)}; + fail -> + try_binding(Rest, PathTokens, ExtraDepth) + end. bind_path([], [], Bindings, Depth) -> {ok, [], Bindings, Depth}; @@ -132,41 +97,16 @@ bind_path([?MATCH_ALL], PathRest, Bindings, Depth) when is_list(PathRest) -> {ok, PathRest, Bindings, Depth + length(PathRest)}; bind_path(_, [], _, _) -> fail; -bind_path([Token|Rest], [Match|PathRest], Bindings, Depth) when is_atom(Token) -> +bind_path([Token|Rest],[Match|PathRest],Bindings,Depth) when is_atom(Token) -> bind_path(Rest, PathRest, [{Token, Match}|Bindings], Depth + 1); bind_path([Token|Rest], [Token|PathRest], Bindings, Depth) -> bind_path(Rest, PathRest, Bindings, Depth + 1); bind_path(_, _, _, _) -> fail. -try_binding([], PathTokens, _) -> - {no_dispatch_match, PathTokens}; -try_binding([{PathSchema, Mod, Props}|Rest], PathTokens, ExtraDepth) -> - case bind_path(PathSchema, PathTokens, [], 0) of - {ok, Remainder, Bindings, Depth} -> - {Mod, Props, Remainder, Bindings, calculate_app_root(Depth + ExtraDepth), reconstitute(Remainder)}; - fail -> - try_binding(Rest, PathTokens, ExtraDepth) - end. - -reconstitute([]) -> - ""; -reconstitute(UnmatchedTokens) -> - string:join(UnmatchedTokens, [?SEPARATOR]). - -binding_dispatch(DispatchList, Req, Client) -> - PathAsString = Req:path(), - Path = string:tokens(PathAsString, [?SEPARATOR]), - % URIs that end with a trailing slash are implicitly one token - % "deeper" than we otherwise might think as we are "inside" - % a directory named by the last token. - ExtraDepth = case lists:last(PathAsString) == ?SEPARATOR of - true -> 1; - _ -> 0 - end, - gen_server:reply(Client, try_binding(DispatchList, Path, ExtraDepth)). +reconstitute([]) -> ""; +reconstitute(UnmatchedTokens) -> string:join(UnmatchedTokens, [?SEPARATOR]). -calculate_app_root(1) -> - "."; +calculate_app_root(1) -> "."; calculate_app_root(N) when N > 1 -> string:join(lists:duplicate(N, ".."), [?SEPARATOR]). diff --git a/src/webmachine_mochiweb.erl b/src/webmachine_mochiweb.erl index 6feab7a3..d9722a5e 100644 --- a/src/webmachine_mochiweb.erl +++ b/src/webmachine_mochiweb.erl @@ -39,8 +39,8 @@ start(Options) -> _ -> ignore end, - webmachine_sup:start_dispatcher(DispatchList), - webmachine_dispatcher:set_error_handler(ErrorHandler), + application:set_env(webmachine, dispatch_list, DispatchList), + application:set_env(webmachine, error_handler, ErrorHandler), mochiweb_http:start([{name, ?MODULE}, {loop, fun loop/1} | Options4]). stop() -> @@ -48,9 +48,10 @@ stop() -> loop(MochiReq) -> Req = webmachine:new_request(mochiweb, MochiReq), - case webmachine_dispatcher:dispatch(Req) of + {ok, DispatchList} = application:get_env(webmachine, dispatch_list), + case webmachine_dispatcher:dispatch(Req:path(), DispatchList) of {no_dispatch_match, _UnmatchedPathTokens} -> - ErrorHandler = webmachine_dispatcher:get_error_handler(), + {ok, ErrorHandler} = application:get_env(webmachine, error_handler), ErrorHTML = ErrorHandler:render_error(404, Req, {none, none, []}), Req:append_to_response_body(ErrorHTML), Req:send_response(404), diff --git a/src/webmachine_sup.erl b/src/webmachine_sup.erl index a09c7e7c..97b58f49 100644 --- a/src/webmachine_sup.erl +++ b/src/webmachine_sup.erl @@ -21,7 +21,7 @@ -behaviour(supervisor). %% External exports --export([start_link/0, upgrade/0, start_dispatcher/1, start_logger/1]). +-export([start_link/0, upgrade/0, start_logger/1]). -export([start_perf_logger/1]). %% supervisor callbacks @@ -32,17 +32,6 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). -%% @spec stat_dispatcher([dispatch_tuple()]) -> ok -%% @doc Starts a supervised webmachine_dispatcher process to -%% handle dispatch based on the list of dispatch tuples. -%% See webmachine_dispatcher for the dispatch_tuple(). -start_dispatcher(DispatchList) -> - ChildSpec = - {webmachine_dispatcher, - {webmachine_dispatcher, start_link, [DispatchList]}, - permanent, 5000, worker, dynamic}, - supervisor:start_child(?MODULE, ChildSpec). - start_logger(BaseDir) -> LoggerModule = case application:get_env(webmachine, webmachine_logger_module) of