1 |
|
%% @doc Manage the configuration and initialization of HTTP handlers |
2 |
|
|
3 |
|
-module(mongoose_http_handler). |
4 |
|
|
5 |
|
-export([config_spec/0, process_config/2, cowboy_host/1, get_routes/1]). |
6 |
|
|
7 |
|
-type options() :: #{host := '_' | string(), |
8 |
|
path := string(), |
9 |
|
module := module(), |
10 |
|
atom() => any()}. |
11 |
|
|
12 |
|
-type path() :: iodata(). |
13 |
|
-type routes() :: [{path(), module(), #{atom() => any()}}]. |
14 |
|
|
15 |
|
-export_type([options/0, path/0, routes/0]). |
16 |
|
|
17 |
|
-callback config_spec() -> mongoose_config_spec:config_section(). |
18 |
|
-callback routes(options()) -> routes(). |
19 |
|
|
20 |
|
-optional_callbacks([config_spec/0, routes/1]). |
21 |
|
|
22 |
|
-include("mongoose.hrl"). |
23 |
|
-include("mongoose_config_spec.hrl"). |
24 |
|
|
25 |
|
%% @doc Return a config section with a list of sections for each handler type |
26 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
27 |
|
config_spec() -> |
28 |
53 |
Items = maps:from_list([{atom_to_binary(Module), |
29 |
|
#list{items = handler_config_spec(Module), |
30 |
|
wrap = none}} |
31 |
53 |
|| Module <- configurable_handler_modules()]), |
32 |
53 |
#section{items = Items#{default => #list{items = common_handler_config_spec(), |
33 |
|
wrap = none}}, |
34 |
|
format_items = list, |
35 |
|
validate_keys = module, |
36 |
|
include = always}. |
37 |
|
|
38 |
|
%% @doc Return a config section with handler options |
39 |
|
-spec handler_config_spec(module()) -> mongoose_config_spec:config_section(). |
40 |
|
handler_config_spec(Module) -> |
41 |
212 |
mongoose_config_utils:merge_sections(common_handler_config_spec(), Module:config_spec()). |
42 |
|
|
43 |
|
common_handler_config_spec() -> |
44 |
265 |
#section{items = #{<<"host">> => #option{type = string, |
45 |
|
validate = non_empty, |
46 |
|
process = fun ?MODULE:cowboy_host/1}, |
47 |
|
<<"path">> => #option{type = string} |
48 |
|
}, |
49 |
|
required = [<<"host">>, <<"path">>], |
50 |
|
process = fun ?MODULE:process_config/2}. |
51 |
|
|
52 |
|
process_config([item, HandlerType | _], Opts) -> |
53 |
477 |
Opts#{module => binary_to_atom(HandlerType)}. |
54 |
|
|
55 |
|
%% @doc Return the list of Cowboy routes for the specified handler configuration. |
56 |
|
%% Cowboy will search for a matching Host, then for a matching Path. If no Path matches, |
57 |
|
%% Cowboy will not search for another matching Host. So, we must merge all Paths for each Host, |
58 |
|
%% add any wildcard Paths to each Host, and ensure that the wildcard Host '_' is listed last. |
59 |
|
%% A proplist ensures that the user can influence Host ordering if wildcards like "[...]" are used. |
60 |
|
-spec get_routes([options()]) -> cowboy_router:routes(). |
61 |
|
get_routes(Handlers) -> |
62 |
402 |
Routes = lists:foldl(fun add_handler_routes/2, [], Handlers), |
63 |
402 |
WildcardPaths = proplists:get_value('_', Routes, []), |
64 |
402 |
Merge = fun(Paths) -> Paths ++ WildcardPaths end, |
65 |
402 |
Merged = lists:keymap(Merge, 2, proplists:delete('_', Routes)), |
66 |
402 |
Final = Merged ++ [{'_', WildcardPaths}], |
67 |
402 |
?LOG_DEBUG(#{what => configured_cowboy_routes, routes => Final}), |
68 |
402 |
Final. |
69 |
|
|
70 |
|
add_handler_routes(#{host := Host, path := Path, module := Module} = HandlerOpts, Routes) -> |
71 |
512 |
HandlerRoutes = case mongoose_lib:is_exported(Module, routes, 1) of |
72 |
292 |
true -> Module:routes(HandlerOpts); |
73 |
220 |
false -> [{Path, Module, HandlerOpts}] |
74 |
|
end, |
75 |
512 |
HostRoutes = proplists:get_value(Host, Routes, []), |
76 |
512 |
lists:keystore(Host, 1, Routes, {Host, HostRoutes ++ HandlerRoutes}). |
77 |
|
|
78 |
|
%% @doc Translate "_" used in TOML to '_' expected by COwboy |
79 |
371 |
cowboy_host("_") -> '_'; |
80 |
106 |
cowboy_host(Host) -> Host. |
81 |
|
|
82 |
|
%% @doc All handlers implementing config_spec/0 are listed here |
83 |
|
configurable_handler_modules() -> |
84 |
53 |
[mod_websockets, |
85 |
|
mongoose_client_api, |
86 |
|
mongoose_admin_api, |
87 |
|
mongoose_graphql_handler]. |