Skip to content
26 changes: 16 additions & 10 deletions src/nova_basic_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -304,18 +304,24 @@ handle_view(View, Variables, Options, Req) ->
{ok, Req2}.

render_dtl(View, Variables, Options) ->
case code:is_loaded(View) of
false ->
case code:load_file(View) of
{error, Reason} ->
%% Cast a warning since the module could not be found
?LOG_ERROR(#{msg => <<"Nova could not render template">>, template => View, reason => Reason}),
%% Erlang's code server will auto-load modules on first call
%% Try to render and catch cases where module or function doesn't exist
try View:render(Variables, Options) of
Result -> Result
catch
error:undef:Stacktrace ->
%% Check if the error is specifically from View:render/2
%% (arity 2 is the standard erlydtl render function signature)
case Stacktrace of
[{View, render, 2, _} | _] ->
%% Module doesn't exist or render/2 not exported
?LOG_ERROR(#{msg => <<"Nova could not render template">>,
template => View, reason => module_not_found}),
throw({404, {template_not_found, View}});
_ ->
View:render(Variables, Options)
end;
_ ->
View:render(Variables, Options)
%% undef error from somewhere else, re-raise it
erlang:raise(error, undef, Stacktrace)
end
end.


Expand Down
23 changes: 15 additions & 8 deletions src/nova_router.erl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ compile(Apps) ->
when Req::cowboy_req:req(),
Env0::cowboy_middleware:env().
execute(Req = #{host := Host, path := Path, method := Method}, Env) ->
%% Cache the storage backend lookup to avoid repeated calls
StorageBackend = application:get_env(nova, dispatch_backend, persistent_term),
Dispatch = StorageBackend:get(nova_dispatch),
case routing_tree:lookup(Host, Path, Method, Dispatch) of
Expand Down Expand Up @@ -133,11 +134,9 @@ lookup_url(Host, Path) ->
lookup_url(Host, Path, '_').

lookup_url(Host, Path, Method) ->
%% Fetch both values together to avoid duplicate application:get_env calls
StorageBackend = application:get_env(nova, dispatch_backend, persistent_term),
Dispatch = StorageBackend:get(nova_dispatch),
lookup_url(Host, Path, Method, Dispatch).

lookup_url(Host, Path, Method, Dispatch) ->
routing_tree:lookup(Host, Path, Method, Dispatch).

%%--------------------------------------------------------------------
Expand All @@ -151,6 +150,7 @@ lookup_url(Host, Path, Method, Dispatch) ->
add_routes(_App, []) -> ok;
add_routes(App, [Routes|Tl]) when is_list(Routes) ->
Options = #{},
%% Cache the storage backend to avoid repeated application:get_env calls
StorageBackend = application:get_env(nova, dispatch_backend, persistent_term),
Dispatch = StorageBackend:get(nova_dispatch),

Expand Down Expand Up @@ -184,8 +184,10 @@ add_routes(App, Routes) ->
get_routes(Router, Env) ->
%% Call the router
Controllers = apply_callback(Router, controllers, [Env]),
apply_callback(Router, routes, [Env])
++ lists:append([nova_controller:routes(C, Env) || C <- Controllers ]).
RouterRoutes = apply_callback(Router, routes, [Env]),
ControllerRoutes = [nova_controller:routes(C, Env) || C <- Controllers],
%% Concatenate all route lists into one using a single list operation
lists:append([RouterRoutes | ControllerRoutes]).

%% yields an empty list if callback does not exist
apply_callback(Module, Function, Args) ->
Expand Down Expand Up @@ -225,7 +227,9 @@ compile([App|Tl], Dispatch, Options) ->
CompileParameters = Router:module_info(compile),

RouterFile = proplists:get_value(source, CompileParameters),
Options1 = Options#{app => App, router_file => RouterFile},
%% Cache global plugins in Options to avoid repeated lookups in compile_paths
GlobalPlugins = application:get_env(nova, plugins, []),
Options1 = Options#{app => App, router_file => RouterFile, global_plugins => GlobalPlugins},

{ok, Dispatch1, Options2} = compile_paths(Routes, Dispatch, Options1),

Expand All @@ -244,8 +248,8 @@ compile_paths([], Dispatch, Options) -> {ok, Dispatch, Options};
compile_paths([RouteInfo|Tl], Dispatch, Options) ->
App = maps:get(app, Options),
RouterFile = maps:get(router_file, Options),
%% Fetch the global plugins
GlobalPlugins = application:get_env(nova, plugins, []),
%% Use cached global plugins from Options instead of repeated lookups
GlobalPlugins = maps:get(global_plugins, Options, []),
Plugins = maps:get(plugins, RouteInfo, GlobalPlugins),

Secure =
Expand Down Expand Up @@ -396,6 +400,7 @@ render_status_page(StatusCode, Req) ->
-spec render_status_page(StatusCode :: integer(), Data :: map(), Req :: cowboy_req:req()) ->
{ok, Req0 :: cowboy_req:req(), Env :: map()}.
render_status_page(StatusCode, Data, Req) ->
%% Fetch backend and dispatch together
StorageBackend = application:get_env(nova, dispatch_backend, persistent_term),
Dispatch = StorageBackend:get(nova_dispatch),
render_status_page('_', StatusCode, Data, Req, #{dispatch => Dispatch}).
Expand All @@ -406,6 +411,7 @@ render_status_page(StatusCode, Data, Req) ->
Req :: cowboy_req:req(),
Env :: map()) -> {ok, Req0 :: cowboy_req:req(), Env :: map()}.
render_status_page(Host, StatusCode, Data, Req, Env) ->
%% Cache storage backend lookup
StorageBackend = application:get_env(nova, dispatch_backend, persistent_term),
Dispatch = StorageBackend:get(nova_dispatch),
{Req0, Env0} =
Expand Down Expand Up @@ -446,6 +452,7 @@ insert(Host, Path, Combinator, Value, Tree) ->


add_plugin(Plugin) ->
%% Cache storage backend to avoid repeated lookups
StorageBackend = application:get_env(nova, dispatch_backend, persistent_term),
StoredPlugins = StorageBackend:get(?NOVA_PLUGINS, []),
Plugins1 = lists:umerge([[Plugin], StoredPlugins]),
Expand Down
3 changes: 2 additions & 1 deletion src/nova_session.erl
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ delete(Req, Key) ->
%%% Private functions
%%%===================================================================
get_session_module() ->
application:get_env(nova, session_manager, nova_session_ets).
%% Use cached value from persistent_term for better performance
persistent_term:get(nova_session_manager, nova_session_ets).

get_session_id(Req) ->
case nova:get_env(use_sessions, true) of
Expand Down
2 changes: 2 additions & 0 deletions src/nova_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ init([]) ->
Configuration = application:get_env(nova, cowboy_configuration, #{}),

SessionManager = application:get_env(nova, session_manager, nova_session_ets),
%% Cache session manager in persistent_term for faster lookups in hot path
persistent_term:put(nova_session_manager, SessionManager),

Children0 = [
child(nova_handlers, nova_handlers),
Expand Down