The good news is, you can change the dispatch list at any time, via
application:set_env (webmachine, dispatch_list, NewDispatchList).This does not upset webmachine and it will see the change immediately. So for a single application leveraging webmachine, this seemed as simple as having a code_change handler execute something like
application:set_env(drurly is the name of the application in this case). Now my modules export a function like
(webmachine,
dispatch_list,
lists:foldl (fun (Mod, Acc) ->
case catch Mod:dispatch_rules () of
{ 'EXIT', _ } -> Acc;
X -> X ++ Acc
end
end,
[],
element (2, application:get_key (drurly, modules)))).
if they are webmachine resources.
dispatch_rules () ->
[ { [ "clip" ], ?MODULE, [] },
{ [ "clip", id ], ?MODULE, [] }
].
The problem is that order is important: the webmachine_dispatch_list is consulted in order, and the first match is executed. To solve this I created a sort function which orders the dispatch rules by "specificity"
path_spec_priority ('*') -> 3;Basically:
path_spec_priority (X) when is_atom (X) -> 2;
path_spec_priority (X) when is_list (X) -> 1.
dispatch_specificity ({ PathSpecA, _, _ },
{ PathSpecB, _, _ }) ->
case erlang:length (PathSpecA) - erlang:length (PathSpecB) of
X when X > 0 ->
true;
X when X < 0 ->
false;
_ ->
PrioPathSpecA = [ path_spec_priority (X) || X <- PathSpecA ],
PrioPathSpecB = [ path_spec_priority (X) || X <- PathSpecB ],
case PrioPathSpecA =< PrioPathSpecB of
false ->
false;
true ->
FullPathSpecA = [ { path_spec_priority (X), X } || X <- PathSpecA ],
FullPathSpecB = [ { path_spec_priority (X), X } || X <- PathSpecB ],
FullPathSpecA =< FullPathSpecB
end
end.
- Longer dispatch paths come first.
- If two dispatch paths have equal length, the more specific one comes first, where specificity is defined by examining the elements left-to-right, with
- string literals are most specific
- atoms except '*' are the next most specific
- '*' is the least specific
- If two dispatch paths have equal length and specificity, sort by Erlang term order (effectively, break ties arbitrarily)
application:set_envThis has proven sufficient for a single application running webmachine.
(webmachine,
dispatch_list,
lists:sort
(fun dispatch_specificity/2,
lists:foldl (fun (Mod, Acc) ->
case catch Mod:dispatch_rules () of
{ 'EXIT', _ } -> Acc;
X -> X ++ Acc
end
end,
[],
element (2, application:get_key (drurly, modules))))).
For multiple applications that want to run under the same webmachine, I suspect the right way to go is to have a gen_server which
- contains webmachine dispatch configuration "fragments" per key, where the key is intended to be the application name;
- accepts commands to replace or delete a particular fragment by key;
- for any replace or delete, rebuilds the complete dispatch list by concatenating all fragments together and sorting by specificity, and then updates the webmachine application env var.
I do think order based dispatch is just wrong, and specificity is a much better idea in general. It would be nice to push this back to webmachine. You could provide a sort function perhaps.
ReplyDeleteI think order-based is the safest thing for a general library to implement, because it allows the user to implement any concept of priority on top of it.
ReplyDeleteHowever I agree that the concept of specificity defined in the post will probably capture most user's needs, so getting it back into webmachine as a sort function makes sense. I'll ping them.
The application controller returns old data during a code change handler, solution is here: http://dukesoferl.blogspot.com/2009/09/application-variables-during-upgrade.html
ReplyDelete