Current section

Files

Jump to
nova src nova_router.erl
Raw

src/nova_router.erl

%%%-------------------------------------------------------------------
%%% @author Niclas Axelsson <niclas@burbas.se>
%%% @copyright (C) 2018, Niclas Axelsson
%%% @doc
%%%
%%% @end
%%% Created : 24 Jun 2018 by Niclas Axelsson <niclas@burbas.se>
%%%-------------------------------------------------------------------
-module(nova_router).
-behaviour(gen_server).
%% API
-export([
start_link/0,
get_main_app/0,
process_routefile/2,
add_route/5,
add_route/6,
add_route/7,
apply_routes/0,
remove_route/2,
get_routes/0
]).
%% gen_server callbacks
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-define(SERVER, ?MODULE).
-record(state, {
main_app :: atom(),
listener = nova_listener :: atom(),
dispatch_table = [] :: list()
}).
%%%===================================================================
%%% API
%%%===================================================================
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
%% @end
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
get_main_app() ->
gen_server:call(?SERVER, get_main_app).
process_routefile(App, Routefile) ->
gen_server:cast(?SERVER, {process_routes, App, Routefile}).
add_route(App, Module, Func, Route, Security) ->
add_route(App, Module, Func, '_', Route, Security).
add_route(App, Module, Func, Host, Route, Security) ->
add_route(App, Module, Func, Host, Route, Security, '_').
add_route(App, Module, Func, Host, Route, Security, Method) ->
gen_server:cast(?SERVER, {add_route, App, Module, Func, Host, Route, Security, Method}).
add_static(App, Path, Host, Route) ->
gen_server:cast(?SERVER, {add_static, App, Path, Host, Route}).
apply_routes() ->
gen_server:cast(?SERVER, apply_routes).
remove_route(Host, Route) ->
gen_server:cast(?SERVER, {remove_route, Host, Route}).
get_routes() ->
gen_server:call(?SERVER, get_routes).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([]) ->
Apps =
case application:get_env(nova_applications) of
undefined -> [];
{ok, AppsList} -> AppsList
end,
lists:foreach(fun load_app_route/1, Apps),
{ok, MainApp} = application:get_application(),
process_flag(trap_exit, true),
{ok, #state{main_app = MainApp}}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @spec handle_call(Request, From, State) ->
%% {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_call(get_main_app, _From, State = #state{main_app = Application}) ->
{reply, {ok, Application}, State};
handle_call(get_routes, _From, State = #state{dispatch_table = DT}) ->
{reply, {ok, DT}, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @spec handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_cast({process_routes, App, Routefile}, State) ->
{ok, HostRoutes} = file:consult(Routefile),
process_routes(App, HostRoutes),
apply_routes(),
{noreply, State};
handle_cast({remove_route, Host, Route}, State = #state{dispatch_table = DT}) ->
case proplists:get_value(Host, DT) of
undefined ->
logger:warning("Could not remove route: ~p for host: ~p. Host not found!", [Route, Host]),
{noreply, State};
{Host, Routes} ->
NewRoutes = proplists:delete(Route, Routes),
case NewRoutes of
Routes ->
logger:warning("Could not remove route: ~p for host: ~p. Route not found!", [Route, Host]),
{noreply, State};
_ ->
logger:info("Removed route: ~p from host: ~p", [Route, Host]),
DT2 = proplists:delete(Host, DT),
NewDT = [{Host, NewRoutes}|DT2],
{noreply, State#state{dispatch_table = NewDT}}
end
end;
handle_cast(apply_routes, State = #state{dispatch_table = DT,
listener = Listener}) ->
logger:info("Applying routes. ~p", [DT]),
Dispatch = cowboy_router:compile(DT),
cowboy:set_env(Listener, dispatch, Dispatch),
{noreply, State};
handle_cast({add_static, App, Path, Host, Route}, State = #state{dispatch_table = DT}) ->
RouteInfo = {Route, cowboy_static, {priv_dir, App, Path}},
NewDT =
case proplists:get_value(Host, DT) of
undefined ->
[{Host, [RouteInfo]}|DT];
Routes ->
[{Host, [RouteInfo|Routes]}|proplists:delete(Host, DT)]
end,
{noreply, State#state{dispatch_table = NewDT}};
handle_cast({add_route, App, Module, Func, Host, Route, Secure, Method}, State = #state{dispatch_table = DT}) ->
InitialState = #{app => App,
mod => Module,
func => Func,
secure => Secure,
method => get_method(Method)},
RouteInfo = {Route, nova_controller, InitialState},
NewDT =
case proplists:get_value(Host, DT) of
undefined ->
[{Host, [RouteInfo]}|DT];
Routes ->
[{Host, [RouteInfo|Routes]}|proplists:delete(Host, DT)]
end,
{noreply, State#state{dispatch_table = NewDT}};
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
load_app_route(#{name := AppName, routes_file := RouteFile}) ->
case code:lib_dir(AppName) of
{error, bad_name} ->
logger:warning("Could not find the application ~p. Check your config and rerun the application", [AppName]),
ok;
Filepath ->
RouteFilePath = filename:join([Filepath, RouteFile]),
process_routefile(AppName, RouteFilePath)
end;
load_app_route(RouteInfo = #{name := AppName}) ->
Routename = "priv/" ++ erlang:atom_to_list(AppName) ++ ".routes.erl",
load_app_route(RouteInfo#{routes_file => Routename}).
process_routes(_, []) ->
ok;
process_routes(App, [RoutesLine|T]) ->
Prefix = maps:get(prefix, RoutesLine, ""),
Host = maps:get(host, RoutesLine, '_'),
Routes = maps:get(routes, RoutesLine, []),
Statics = maps:get(statics, RoutesLine, []),
Secure = maps:get(security, RoutesLine, false),
add_routes(App, Host, Prefix, Secure, Routes),
add_statics(App, Host, Prefix, Statics),
process_routes(App, T).
add_routes(_, _, _, _, []) ->
ok;
add_routes(App, Host, Prefix, Secure, [{Route, Module, Func} | T]) ->
add_route(App, Module, Func, Host, Prefix ++ Route, Secure),
add_routes(App, Host, Prefix, Secure, T);
add_routes(App, Host, Prefix, Secure, [{Route, Method, Module, Func} | T]) ->
add_route(App, Module, Func, Host, Prefix ++ Route, Secure, Method),
add_routes(App, Host, Prefix, Secure, T).
add_statics(_, _, _, []) ->
ok;
add_statics(App, Host, Prefix, [{Route, Path} | T]) ->
add_static(App, Path, Host, Prefix ++ Route),
add_statics(App, Host, Prefix, T).
get_method(get) -> <<"GET">>;
get_method(post) -> <<"POST">>;
get_method(put) -> <<"PUT">>;
get_method(delete) -> <<"DELETE">>;
get_method(Others) -> Others.