Browse Source

Add persistent_term support to the router

Loïc Hoguin 5 years ago
parent
commit
cf84f59d9b

+ 25 - 1
doc/src/guide/routing.asciidoc

@@ -210,13 +210,34 @@ Dispatch = cowboy_router:compile([
     %% {HostMatch, list({PathMatch, Handler, InitialState})}
     %% {HostMatch, list({PathMatch, Handler, InitialState})}
     {'_', [{'_', my_handler, #{}}]}
     {'_', [{'_', my_handler, #{}}]}
 ]),
 ]),
-%% Name, NbAcceptors, TransOpts, ProtoOpts
+%% Name, TransOpts, ProtoOpts
 cowboy:start_clear(my_http_listener,
 cowboy:start_clear(my_http_listener,
     [{port, 8080}],
     [{port, 8080}],
     #{env => #{dispatch => Dispatch}}
     #{env => #{dispatch => Dispatch}}
 ).
 ).
 ----
 ----
 
 
+=== Using persistent_term
+
+The routes can be stored in `persistent_term` starting from
+Erlang/OTP 21.2. This may give a performance improvement when
+there are a large number of routes.
+
+To use this functionality you need to compile the routes,
+store them in `persistent_term` and then inform Cowboy:
+
+[source,erlang]
+----
+Dispatch = cowboy_router:compile([
+    {'_', [{'_', my_handler, #{}}]}
+]),
+persistent_term:put(my_app_dispatch, Dispatch),
+cowboy:start_clear(my_http_listener,
+    [{port, 8080}],
+    #{env => #{dispatch => {persistent_term, my_app_dispatch}}}
+).
+----
+
 === Live update
 === Live update
 
 
 You can use the `cowboy:set_env/3` function for updating the dispatch
 You can use the `cowboy:set_env/3` function for updating the dispatch
@@ -228,3 +249,6 @@ Dispatch = cowboy_router:compile(Routes),
 cowboy:set_env(my_http_listener, dispatch, Dispatch).
 cowboy:set_env(my_http_listener, dispatch, Dispatch).
 
 
 Note that you need to compile the routes again before updating.
 Note that you need to compile the routes again before updating.
+
+When using `persistent_term` there is no need to call this function,
+you can simply put the new routes in the storage.

+ 4 - 1
doc/src/manual/cowboy_router.asciidoc

@@ -13,7 +13,10 @@ The router takes the `dispatch` rules as input from the
 middleware environment. Dispatch rules are generated by
 middleware environment. Dispatch rules are generated by
 calling the
 calling the
 link:man:cowboy_router:compile(3)[cowboy_router:compile(3)]
 link:man:cowboy_router:compile(3)[cowboy_router:compile(3)]
-function.
+function. The environment can contain the rules directly
+or a tuple `{persistent_term, Key}`, in which case Cowboy
+will call `persistent_term:get(Key)` to retrieve the
+dispatch rules.
 
 
 When a route matches, the router sets the `handler` and
 When a route matches, the router sets the `handler` and
 `handler_opts` middleware environment values containing
 `handler_opts` middleware environment values containing

+ 5 - 1
src/cowboy_router.erl

@@ -160,7 +160,11 @@ compile_brackets_split(<< C, Rest/bits >>, Acc, N) ->
 -spec execute(Req, Env)
 -spec execute(Req, Env)
 	-> {ok, Req, Env} | {stop, Req}
 	-> {ok, Req, Env} | {stop, Req}
 	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
 	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
-execute(Req=#{host := Host, path := Path}, Env=#{dispatch := Dispatch}) ->
+execute(Req=#{host := Host, path := Path}, Env=#{dispatch := Dispatch0}) ->
+	Dispatch = case Dispatch0 of
+		{persistent_term, Key} -> persistent_term:get(Key);
+		_ -> Dispatch0
+	end,
 	case match(Dispatch, Host, Path) of
 	case match(Dispatch, Host, Path) of
 		{ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->
 		{ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->
 			{ok, Req#{
 			{ok, Req#{

+ 23 - 0
test/http2_SUITE.erl

@@ -196,6 +196,29 @@ max_frame_size_sent(Config) ->
 		cowboy:stop_listener(?FUNCTION_NAME)
 		cowboy:stop_listener(?FUNCTION_NAME)
 	end.
 	end.
 
 
+persistent_term_router(Config) ->
+	doc("The router can retrieve the routes from persistent_term storage."),
+	case erlang:function_exported(persistent_term, get, 1) of
+		true -> do_persistent_term_router(Config);
+		false -> {skip, "This test uses the persistent_term functionality added in Erlang/OTP 21.2."}
+	end.
+
+do_persistent_term_router(Config) ->
+	persistent_term:put(?FUNCTION_NAME, init_dispatch(Config)),
+	{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
+		env => #{dispatch => {persistent_term, ?FUNCTION_NAME}}
+	}),
+	Port = ranch:get_port(?FUNCTION_NAME),
+	try
+		ConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]),
+		{ok, http2} = gun:await_up(ConnPid),
+		StreamRef = gun:get(ConnPid, "/"),
+		{response, nofin, 200, _} = gun:await(ConnPid, StreamRef),
+		gun:close(ConnPid)
+	after
+		cowboy:stop_listener(?FUNCTION_NAME)
+	end.
+
 preface_timeout_infinity(Config) ->
 preface_timeout_infinity(Config) ->
 	doc("Ensure infinity for preface_timeout is accepted."),
 	doc("Ensure infinity for preface_timeout is accepted."),
 	ProtoOpts = #{
 	ProtoOpts = #{

+ 23 - 0
test/http_SUITE.erl

@@ -250,6 +250,29 @@ idle_timeout_infinity(Config) ->
 		cowboy:stop_listener(?FUNCTION_NAME)
 		cowboy:stop_listener(?FUNCTION_NAME)
 	end.
 	end.
 
 
+persistent_term_router(Config) ->
+	doc("The router can retrieve the routes from persistent_term storage."),
+	case erlang:function_exported(persistent_term, get, 1) of
+		true -> do_persistent_term_router(Config);
+		false -> {skip, "This test uses the persistent_term functionality added in Erlang/OTP 21.2."}
+	end.
+
+do_persistent_term_router(Config) ->
+	persistent_term:put(?FUNCTION_NAME, init_dispatch(Config)),
+	{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
+		env => #{dispatch => {persistent_term, ?FUNCTION_NAME}}
+	}),
+	Port = ranch:get_port(?FUNCTION_NAME),
+	try
+		ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),
+		{ok, http} = gun:await_up(ConnPid),
+		StreamRef = gun:get(ConnPid, "/"),
+		{response, nofin, 200, _} = gun:await(ConnPid, StreamRef),
+		gun:close(ConnPid)
+	after
+		cowboy:stop_listener(?FUNCTION_NAME)
+	end.
+
 request_timeout_infinity(Config) ->
 request_timeout_infinity(Config) ->
 	doc("Ensure the request_timeout option accepts the infinity value."),
 	doc("Ensure the request_timeout option accepts the infinity value."),
 	{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{
 	{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{