-
Notifications
You must be signed in to change notification settings - Fork 21
/
nova_handler.erl
122 lines (112 loc) · 5.41 KB
/
nova_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
-module(nova_handler).
-behaviour(cowboy_middleware).
%% Callbacks
-export([
execute/2,
terminate/3
]).
-include("../include/nova_comp.hrl").
-include("../include/nova_logger.hrl").
-include("nova_router.hrl").
-callback init(Req, any()) -> {ok | module(), Req, any()}
| {module(), Req, any(), any()}
when Req::cowboy_req:req().
-callback terminate(any(), map(), any()) -> ok.
-optional_callbacks([terminate/3]).
-spec execute(Req, Env) -> {ok, Req, Env}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
execute(Req, Env = #{cowboy_handler := Handler, arguments := Arguments}) ->
try erlang:apply(Handler, init, [Req, Arguments]) of
{ok, Req2, _State} ->
Result = terminate(normal, Req2, Handler),
{ok, Req2, Env#{result => Result}};
{Mod, Req2, State} ->
erlang:apply(Mod, upgrade, [Req2, Env, Handler, State]);
{Mod, Req2, State, Opts} ->
erlang:apply(Mod, upgrade, [Req2, Env, Handler, State, Opts])
catch
?STACKTRACE(Class, Reason, Stacktrace)
Payload = #{status_code => 500,
stacktrace => Stacktrace,
class => Class,
reason => Reason},
?LOG_ERROR(#{msg => "Controller crashed", class => Class, reason => Reason, stacktrace => Stacktrace}),
render_response(Req#{crash_info => Payload}, Env, 500)
end;
execute(Req, Env = #{module := Module, function := Function}) ->
%% Ensure that the module exists and have the correct function exported
case lists:search(fun({Export, Arity}) -> Export == Function andalso Arity == 1 end,
erlang:apply(Module, module_info, [exports])) of
{value, _} ->
try erlang:apply(Module, Function, [Req]) of
RetObj ->
case nova_handlers:get_handler(element(1, RetObj)) of
{ok, Callback} ->
{ok, Req0} = Callback(RetObj, {Module, Function}, Req),
render_response(Req0, Env);
{error, not_found} ->
?LOG_ERROR(#{msg => "Controller returned unsupported result", controller => Module,
function => Function, return => RetObj})
end
catch
?STACKTRACE(Class, Reason, Stacktrace)
?LOG_ERROR(#{msg => "Controller crashed",
class => Class,
reason => Reason,
stacktrace => Stacktrace}),
terminate(Reason, Req, Module),
%% Build the payload object
Payload = #{status_code => 500,
stacktrace => Stacktrace,
class => Class,
reason => Reason},
render_response(Req#{crash_info => Payload}, Env, 500)
end;
_ ->
?LOG_ERROR(#{msg => "Could not find controller",
controller => Module,
function => io_lib:format("~s/1", [Function])}),
Payload = #{status_code => 500,
status => "Problems with application",
stacktrace => [{Module, Function, 1}],
title => "Woops. It seems like you have a problem with your application!",
extra_msg => "Nova could not find your controller:function.
Pleae check your spelling and/or that the module" ++
" have been properly loaded. "
},
render_response(Req#{crash_info => Payload}, Env, 500)
end.
-spec terminate(any(), Req | undefined, module()) -> ok when Req::cowboy_req:req().
terminate(Reason, Req, Module) ->
case erlang:function_exported(Module, terminate, 3) of
true ->
erlang:apply(Module, terminate, [Reason, Req]);
false ->
ok
end.
%%%%%%%%%%%%%%%%%%%%%%%%
%% INTERNAL FUNCTIONS %%
%%%%%%%%%%%%%%%%%%%%%%%%
-spec render_response(Req :: cowboy_req:req(), Env :: map()) -> {ok, Req :: cowboy_req:req(), State :: map()}.
render_response(Req = #{resp_status_code := StatusCode}, Env) ->
Req0 = cowboy_req:reply(StatusCode, Req),
{ok, Req0, Env};
render_response(Req, Env) ->
Req0 = cowboy_req:reply(200, Req),
{ok, Req0, Env}.
render_response(Req, Env, StatusCode) ->
case application:get_env(nova, render_error_pages, true) of
true ->
case nova_router:lookup_url(StatusCode) of
{error, _} ->
%% Render the internal view of nova
{ok, Req0} = nova_basic_handler:handle_status({status, StatusCode}, {dummy, dummy}, Req),
render_response(Req0, Env);
{ok, _Bindings, #nova_handler_value{module = EMod, function = EFunc}} ->
%% Recurse to the execute function to render error page
execute(Req, Env#{app => nova, module => EMod, function => EFunc})
end;
false ->
{ok, Req0} = nova_basic_handler:handle_status({status, StatusCode}, {dummy, dummy}, Req),
render_response(Req0, Env)
end.