./ct_report/coverage/mongoose_http_handler.COVER.html

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 33 Items = maps:from_list([{atom_to_binary(Module),
29 #list{items = handler_config_spec(Module),
30 wrap = none}}
31 33 || Module <- configurable_handler_modules()]),
32 33 #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 132 mongoose_config_utils:merge_sections(common_handler_config_spec(), Module:config_spec()).
42
43 common_handler_config_spec() ->
44 165 #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 330 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 297 Routes = lists:foldl(fun add_handler_routes/2, [], Handlers),
63 297 WildcardPaths = proplists:get_value('_', Routes, []),
64 297 Merge = fun(Paths) -> Paths ++ WildcardPaths end,
65 297 Merged = lists:keymap(Merge, 2, proplists:delete('_', Routes)),
66 297 Final = Merged ++ [{'_', WildcardPaths}],
67 297 ?LOG_DEBUG(#{what => configured_cowboy_routes, routes => Final}),
68 297 Final.
69
70 add_handler_routes(#{host := Host, path := Path, module := Module} = HandlerOpts, Routes) ->
71 367 HandlerRoutes = case mongoose_lib:is_exported(Module, routes, 1) of
72 227 true -> Module:routes(HandlerOpts);
73 140 false -> [{Path, Module, HandlerOpts}]
74 end,
75 367 HostRoutes = proplists:get_value(Host, Routes, []),
76 367 lists:keystore(Host, 1, Routes, {Host, HostRoutes ++ HandlerRoutes}).
77
78 %% @doc Translate "_" used in TOML to '_' expected by COwboy
79 264 cowboy_host("_") -> '_';
80 66 cowboy_host(Host) -> Host.
81
82 %% @doc All handlers implementing config_spec/0 are listed here
83 configurable_handler_modules() ->
84 33 [mod_websockets,
85 mongoose_client_api,
86 mongoose_admin_api,
87 mongoose_graphql_handler].
Line Hits Source