Packages
nova
0.2.2
0.15.1
0.14.3
0.14.1
0.13.7
0.13.1
0.13.0
0.12.1
0.12.0
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.11
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.4.4
0.4.3
0.4.2
0.4.1
0.2.2
0.1.0
0.0.1
0.0.0
Nova is a web application framework
Current section
Files
Jump to
Current section
Files
src/nova_http_handler.erl
%%% @author Niclas Axelsson <niclas@burbas.se>
%%% @doc
%%% Callback controller for handling http requests
%%% @end
-module(nova_http_handler).
-export([
execute/2
]).
-behaviour(cowboy_middleware).
-include_lib("nova/include/nova.hrl").
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Public functions %
%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%--------------------------------------------------------------------
%% @doc
%% Callback function from cowboy. Checks if the user is allowed to see
%% this page (See check_security/2 function). If true then the handling
%% of the request will continue. If not it will either redirect the user,
%% return a 401 http staus or return a cowboy request object.
%% @end
%%--------------------------------------------------------------------
execute(Req, Env = #{handler_opts := #{mod := Mod, func := Func, methods := '_'}}) ->
handle(Mod, Func, Req, Env);
execute(Req = #{method := ReqMethod}, Env = #{handler_opts := #{mod := Mod, func := Func, methods := Methods}}) ->
case lists:any(fun(X) -> X == ReqMethod end, Methods) of
true ->
handle(Mod, Func, Req, Env);
false ->
Req1 = nova_router:status_page(401, Req),
{stop, Req1}
end;
execute(Req, Env) ->
%% Just continue to next handler
{ok, Req, Env}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Private functions %
%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle(Mod, Fun, Req, Env = #{handler_opts := State}) ->
Args =
case maps:get(auth_data, State, undefined) of
undefined ->
[Req];
SecObject ->
[Req, SecObject]
end,
try erlang:apply(Mod, Fun, Args) of
RetObj ->
handle1(RetObj, Mod, Fun, Req, Env)
catch
?WITH_STACKTRACE(Type, Reason, Stacktrace)
?ERROR("Controller (~p:~p/1) failed with ~p:~p.~nStacktrace:~n~p", [Mod, Fun, Type, Reason, Stacktrace]),
Req1 = nova_router:status_page(404, Req),
{stop, Req1}
end.
handle1(RetObj, Mod, Fun, Req = #{method := Method}, Env) ->
case RetObj of
{json, JSON} ->
EncodedJSON = jsone:encode(JSON, [undefined_as_null]),
StatusCode = case Method of
<<"POST">> -> 201;
_ -> 200
end,
Req1 = cowboy_req:reply(StatusCode, #{
<<"content-type">> => <<"application/json">>
}, EncodedJSON, Req),
{stop, Req1};
{json, StatusCode, Headers, JSON} ->
EncodedJSON = jsone:encode(JSON, [undefined_as_null]),
Req1 = cowboy_req:reply(StatusCode,
maps:merge(#{<<"content-type">> =>
<<"application/json">>},
Headers),
EncodedJSON,
Req),
{stop, Req1};
{ok, Variables} ->
%% Derive the view from module
ViewNameAtom = get_view_name(Mod),
handle_view(ViewNameAtom, Variables, #{}, Req, Env);
{ok, Variables, Options} ->
View =
case maps:get(view, Options, undefined) of
undefined ->
ViewName = atom_to_list(Mod) ++ "_dtl",
list_to_atom(ViewName);
CustomView when is_atom(CustomView) ->
ViewName = atom_to_list(CustomView) ++ "_dtl",
list_to_atom(ViewName);
CustomView ->
list_to_atom(CustomView ++ "_dtl")
end,
handle_view(View, Variables, Options, Req, Env);
{status, Status} when is_integer(Status) ->
Req1 = nova_router:get_status_page(Status, Req),
{stop, Req1};
{status, Status, _Headers} when is_integer(Status) ->
%% TODO! Include headers
Req1 = nova_router:get_status_page(Status, Req),
{ok, Req1};
{redirect, Route} ->
Req1 = cowboy_req:reply(
302,
#{<<"Location">> => list_to_binary(Route)},
Req
),
{stop, Req1};
{cowboy_req, CowboyReq} ->
{stop, CowboyReq};
Other ->
Signature = erlang:element(1, Other),
case nova_router:get_handler(Signature) of
#{module := Handler} ->
try Handler:handle(Other) of
{continue, Result} ->
%% Recurse. Maybe we need something else?
handle1(Result, Mod, Fun, Req, Env);
Result ->
Result
catch
?WITH_STACKTRACE(Type, Reason, Stacktrace)
erlang:raise(Type, Reason, Stacktrace)
end;
_ ->
erlang:throw({unknown_handler_type, Signature})
end
end.
handle_view(View, Variables, Options, Req, Env) ->
{ok, HTML} = render_dtl(View, Variables, []),
Headers =
case maps:get(headers, Options, undefined) of
undefined ->
#{<<"content-type">> => <<"text/html">>};
UserHeaders ->
UserHeaders
end,
StatusCode = maps:get(status_code, Options, 200),
Req1 = cowboy_req:reply(StatusCode, Headers, HTML, Req),
{ok, Req1, Env}.
render_dtl(View, Variables, Options) ->
case code:is_loaded(View) of
false ->
%% Cast a warning since the module could not be found
?WARNING("Could not render ~p cause it's not loaded.", [View]);
_ ->
View:render(Variables, Options)
end.
get_view_name(Mod) when is_atom(Mod) ->
StrName = get_view_name(erlang:atom_to_list(Mod)),
erlang:list_to_atom(StrName);
get_view_name([$_, $c, $o, $n, $t, $r, $o, $l, $l, $e, $r]) ->
"_dtl";
get_view_name([H|T]) ->
[H|get_view_name(T)].
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Eunit functions %
%%%%%%%%%%%%%%%%%%%%%%%%%%%
-ifdef(TEST).
-endif.