-
Notifications
You must be signed in to change notification settings - Fork 66
/
dynamic_route_handler.erl
181 lines (157 loc) · 6.75 KB
/
dynamic_route_handler.erl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
% vim: sw=4 ts=4 et ft=erlang
% Nitrogen Web Framework for Erlang
% Copyright (c) 2008-2010 Rusty Klophaus
% See MIT-LICENSE for licensing information.
-module (dynamic_route_handler).
-behaviour (route_handler).
-include("wf.hrl").
-export ([
init/2,
finish/2
]).
%% @doc
%% dynamic_route_handler looks at the requested path and file extension
%% to determine how a request should be served. If the request path has no
%% extension, then it assumes this request should be handled by a Nitrogen
%% page module that matches the path with slashes converted to underscores.
%% If no module is found, then it will chop off the last part of the path
%% (storing it for later access in wf:path_info/0) and try again, repeating
%% until either a module is found, or there are no more parts to chop. If
%% a module still can't be found, then the web_404 module is used if defined
%% by the user, otherwise a 404 is generated internally.
%%
%% Requests for "/" are automatically sent to index.
%%
%% If the request path does have an extension, then it is treated like a request
%% for a static file. This is delegated back to the HTTP server.
init(_Config, State) ->
% Get the path...
Bridge = wf_context:bridge(),
Path = sbw:path(Bridge),
% Convert the path to a module. If there are no routes defined, then just
% convert everything without an extension to a module.
% Otherwise, look through all routes for the first matching route.
{Module, EntryPoint, PathInfo} = route(Path),
{Module1, PathInfo1} = check_for_404(Module, PathInfo, Path),
wf_context:page_module(Module1),
wf_context:path_info(PathInfo1),
wf_context:entry_point(EntryPoint),
{ok, State}.
finish(_Config, State) ->
{ok, State}.
%%% PRIVATE FUNCTIONS %%%
% First, check if this is a request for the root path. If so
% then just send it to index.
% Check if there is an extension, if so, it's static.
% Otherwise, try to load a module according to each part of the path.
% First, cycle through code:all_loaded(). If not there, then check erl_prim_loader:get_file()
% If still not there, then 404.
route("/") ->
{list_to_atom(module_name(["index"])), main, []};
route(Path) ->
InitialEntryPoint = determine_initial_entry_point(Path),
handle_initial_entry_point(Path, InitialEntryPoint).
handle_initial_entry_point(Path, static) ->
{static_file, main, Path};
handle_initial_entry_point(Path, {module, EntryFun, ProcessingFun}) ->
Path1 = string:strip(filename:rootname(Path), both, $/),
Tokens = string:tokens(Path1, "/"),
% Check for a loaded module. If not found, then try to load it.
case try_load_module(EntryFun, ProcessingFun, Tokens) of
{Module, EntryPoint, PathInfo} ->
{Module, EntryPoint, PathInfo};
undefined ->
{web_404, main, Path1}
end.
determine_initial_entry_point(Filename) ->
SmartExtensions = wf:config_default(smart_extensions, []),
Ext = string:strip(filename:extension(Filename), left, $.),
case lists:keyfind(Ext, 1, SmartExtensions) of
{Ext, EntryFun, ProcessingFun={_,_}} ->
{module, EntryFun, ProcessingFun};
{Ext, EntryFun, undefined} ->
{module, EntryFun, undefined};
{Ext, EntryFun} ->
{module, EntryFun, undefined};
false when Ext==[] ->
{module, main, undefined};
false ->
static;
Other ->
throw({invalid_smart_extension_definition, Other})
end.
module_name(Tokens) ->
ModulePrefix = wf:config_default(module_prefix, ""),
AllTokens = case ModulePrefix of
"" -> Tokens;
_ -> [ ModulePrefix | Tokens ]
end,
_ModuleName = string:join(AllTokens, "_").
verify_and_atomize_module(ModuleName) when is_list(ModuleName) ->
try
list_to_existing_atom(ModuleName)
catch _:_ ->
case erl_prim_loader:get_file(ModuleName ++ ".beam") of
{ok, _, _} -> list_to_atom(ModuleName);
_ -> list_to_atom("$not_found")
end
end.
try_load_module(EntryFun, ProcessingFun, Tokens) ->
try_load_module(EntryFun, ProcessingFun, Tokens, []).
try_load_module(_EntryFun, _ProcessingFun, [], _ExtraTokens) ->
undefined;
try_load_module(EntryFun, ProcessingFun, Tokens, ExtraTokens) ->
ModuleName = module_name(Tokens),
Module = verify_and_atomize_module(ModuleName),
%% Load the module, check if it exports the right method...
code:ensure_loaded(Module),
%% EntryFun will usually be 'main', meaning we're looking for Module:main()
%% to enter by default. Smart Extensions might look for a different entry
%% function like 'json_main'.
case erlang:function_exported(Module, EntryFun, 0) of
true ->
PathInfo = string:join(ExtraTokens, "/"),
RealEntry = case ProcessingFun of
undefined ->
%% There's no processing function, so we just enter the
%% module normally
EntryFun;
{ProcMod, ProcFun} ->
%% We've identified that this has the entry point for the
%% associated smart extension, and this smart extension has
%% a processing Mod:Fun combination, so we'll pass that as
%% the entry point.
fun() -> ProcMod:ProcFun(fun Module:EntryFun/0) end
end,
{Module, RealEntry, PathInfo};
false ->
case is_rest_module(Module) of
true ->
PathInfo = string:join(ExtraTokens, "/"),
%% If this is a rest request, we handle it with the nitrogen_rest module
Entry = fun() -> nitrogen_rest:handle_request(Module) end,
{Module, Entry, PathInfo};
false ->
next_try_load_module(EntryFun, ProcessingFun, Tokens, ExtraTokens)
end
end.
next_try_load_module(EntryFun, ProcessingFun, Tokens, ExtraTokens) ->
Tokens1 = lists:reverse(tl(lists:reverse(Tokens))),
ExtraTokens1 = [hd(lists:reverse(Tokens))|ExtraTokens],
try_load_module(EntryFun, ProcessingFun, Tokens1, ExtraTokens1).
is_rest_module(Module) ->
wf_utils:has_behaviour(Module, nitrogen_rest).
check_for_404(static_file, _PathInfo, Path) ->
{static_file, Path};
check_for_404(Module, PathInfo, Path) ->
% Make sure the requested module is loaded. If it
% is not, then try to load the web_404 page. If that
% is not available, then default to the 'file_not_found_page' module.
case code:ensure_loaded(Module) of
{module, Module} -> {Module, PathInfo};
_ ->
case code:ensure_loaded(web_404) of
{module, web_404} -> {web_404, Path};
_ -> {file_not_found_page, Path}
end
end.