From cf84f59d9bdf819d943b3de8e3bc85c561f88efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Sat, 28 Sep 2019 15:40:41 +0200 Subject: Add persistent_term support to the router --- doc/src/guide/routing.asciidoc | 26 +++++++++++++++++++++++++- doc/src/manual/cowboy_router.asciidoc | 5 ++++- src/cowboy_router.erl | 6 +++++- test/http2_SUITE.erl | 23 +++++++++++++++++++++++ test/http_SUITE.erl | 23 +++++++++++++++++++++++ 5 files changed, 80 insertions(+), 3 deletions(-) diff --git a/doc/src/guide/routing.asciidoc b/doc/src/guide/routing.asciidoc index e5f8c33..9e2ef25 100644 --- a/doc/src/guide/routing.asciidoc +++ b/doc/src/guide/routing.asciidoc @@ -210,13 +210,34 @@ Dispatch = cowboy_router:compile([ %% {HostMatch, list({PathMatch, Handler, InitialState})} {'_', [{'_', my_handler, #{}}]} ]), -%% Name, NbAcceptors, TransOpts, ProtoOpts +%% Name, TransOpts, ProtoOpts cowboy:start_clear(my_http_listener, [{port, 8080}], #{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 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). 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. diff --git a/doc/src/manual/cowboy_router.asciidoc b/doc/src/manual/cowboy_router.asciidoc index 61b8d57..d3c1306 100644 --- a/doc/src/manual/cowboy_router.asciidoc +++ b/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 calling the 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 `handler_opts` middleware environment values containing diff --git a/src/cowboy_router.erl b/src/cowboy_router.erl index 44a7e18..0eb4bdd 100644 --- a/src/cowboy_router.erl +++ b/src/cowboy_router.erl @@ -160,7 +160,11 @@ compile_brackets_split(<< C, Rest/bits >>, Acc, N) -> -spec execute(Req, Env) -> {ok, Req, Env} | {stop, Req} 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 {ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} -> {ok, Req#{ diff --git a/test/http2_SUITE.erl b/test/http2_SUITE.erl index 3af837b..44fc5cc 100644 --- a/test/http2_SUITE.erl +++ b/test/http2_SUITE.erl @@ -196,6 +196,29 @@ max_frame_size_sent(Config) -> cowboy:stop_listener(?FUNCTION_NAME) 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) -> doc("Ensure infinity for preface_timeout is accepted."), ProtoOpts = #{ diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index 7f23da4..0b4edd9 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -250,6 +250,29 @@ idle_timeout_infinity(Config) -> cowboy:stop_listener(?FUNCTION_NAME) 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) -> doc("Ensure the request_timeout option accepts the infinity value."), {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{ -- cgit v1.2.3