diff --git a/apps/emqx_dashboard/etc/emqx_dashboard.conf b/apps/emqx_dashboard/etc/emqx_dashboard.conf index 22c090457..862c95d07 100644 --- a/apps/emqx_dashboard/etc/emqx_dashboard.conf +++ b/apps/emqx_dashboard/etc/emqx_dashboard.conf @@ -14,7 +14,7 @@ dashboard { protocol = http num_acceptors = 4 max_connections = 512 - port = 18083 + bind = 18083 backlog = 512 send_timeout = 5s inet6 = false @@ -23,7 +23,7 @@ dashboard { # , # { # protocol = https - # port = 18084 + # bind = "127.0.0.1:18084" # num_acceptors = 2 # backlog = 512 # send_timeout = 5s diff --git a/apps/emqx_dashboard/src/emqx_dashboard.erl b/apps/emqx_dashboard/src/emqx_dashboard.erl index a8ec70b1b..faa6784e0 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard.erl @@ -61,15 +61,26 @@ start_listeners() -> dispatch => Dispatch, middlewares => [cowboy_router, ?EMQX_MIDDLE, cowboy_handler] }, - [begin - Minirest = maps:put(protocol, Protocol, BaseMinirest), - {ok, _} = minirest:start(Name, RanchOptions, Minirest), - ?ULOG("Start listener ~ts on ~p successfully.~n", [Name, Port]) - end || {Name, Protocol, Port, RanchOptions} <- listeners()]. + Res = + lists:foldl(fun({Name, Protocol, Port, RanchOptions}, Acc) -> + Minirest = BaseMinirest#{protocol => Protocol}, + case minirest:start(Name, RanchOptions, Minirest) of + {ok, _} -> + ?ULOG("Start listener ~ts on ~p successfully.~n", [Name, Port]), + Acc; + {error, _Reason} -> + %% Don't record the reason because minirest already does(too much logs noise). + [Name | Acc] + end + end, [], listeners()), + case Res of + [] -> ok; + _ -> {error, Res} + end. stop_listeners() -> [begin - ok = minirest:stop(Name), + _ = minirest:stop(Name), ?ULOG("Stop listener ~ts on ~p successfully.~n", [Name, Port]) end || {Name, _, Port, _} <- listeners()]. @@ -85,12 +96,19 @@ apps() -> listeners() -> [begin - Protocol = maps:get(protocol, ListenerOptions, http), - Port = maps:get(port, ListenerOptions, 18083), - Name = listener_name(Protocol, Port), - RanchOptions = ranch_opts(maps:without([protocol], ListenerOptions)), + Protocol = maps:get(protocol, ListenerOption0, http), + {ListenerOption, Port} = ip_port(ListenerOption0), + Name = listener_name(Protocol, ListenerOption), + RanchOptions = ranch_opts(maps:without([protocol], ListenerOption)), {Name, Protocol, Port, RanchOptions} - end || ListenerOptions <- emqx_conf:get([dashboard, listeners], [])]. + end || ListenerOption0 <- emqx_conf:get([dashboard, listeners], [])]. + +ip_port(Opts) -> ip_port(maps:take(bind, Opts), Opts). + +ip_port(error, Opts) -> {Opts#{port => 18083}, 18083}; +ip_port({Port, Opts}, _) when is_integer(Port) -> {Opts#{port => Port}, Port}; +ip_port({{IP, Port}, Opts}, _) -> {Opts#{port => Port, ip => IP}, Port}. + ranch_opts(RanchOptions) -> Keys = [ {ack_timeout, handshake_timeout} @@ -119,8 +137,16 @@ key_only(K , true , S) -> [K | S]; key_only(_K, false, S) -> S; key_only(K , V , S) -> [{K, V} | S]. -listener_name(Protocol, Port) -> - Name = "dashboard:" ++ atom_to_list(Protocol) ++ ":" ++ integer_to_list(Port), +listener_name(Protocol, #{port := Port, ip := IP}) -> + Name = "dashboard:" + ++ atom_to_list(Protocol) ++ ":" + ++ inet:ntoa(IP) ++ ":" + ++ integer_to_list(Port), + list_to_atom(Name); +listener_name(Protocol, #{port := Port}) -> + Name = "dashboard:" + ++ atom_to_list(Protocol) ++ ":" + ++ integer_to_list(Port), list_to_atom(Name). authorize(Req) -> diff --git a/apps/emqx_dashboard/src/emqx_dashboard_app.erl b/apps/emqx_dashboard/src/emqx_dashboard_app.erl index 93e8cdc6d..ac54296c4 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_app.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_app.erl @@ -27,10 +27,13 @@ start(_StartType, _StartArgs) -> {ok, Sup} = emqx_dashboard_sup:start_link(), ok = mria_rlog:wait_for_shards([?DASHBOARD_SHARD], infinity), - _ = emqx_dashboard:start_listeners(), - emqx_dashboard_cli:load(), - {ok, _Result} = emqx_dashboard_admin:add_default_user(), - {ok, Sup}. + case emqx_dashboard:start_listeners() of + ok -> + emqx_dashboard_cli:load(), + {ok, _Result} = emqx_dashboard_admin:add_default_user(), + {ok, Sup}; + {error, Reason} -> {error, Reason} + end. stop(_State) -> emqx_dashboard_cli:unload(), diff --git a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl index 957d1e2da..e8ead7431 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_schema.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_schema.erl @@ -25,8 +25,18 @@ namespace() -> <<"dashboard">>. roots() -> ["dashboard"]. fields("dashboard") -> - [ {listeners, hoconsc:array(hoconsc:union([hoconsc:ref(?MODULE, "http"), - hoconsc:ref(?MODULE, "https")]))} + [ {listeners, + sc(hoconsc:array(hoconsc:union([hoconsc:ref(?MODULE, "http"), + hoconsc:ref(?MODULE, "https")])), + #{ desc => +"""HTTP(s) listeners identified by their protocol type, +is used to serve dashboard UI and restful HTTP API.
+Listeners must have a unique combination of port number and IP address.
+For example, an HTTP listener can listen on all configured IP addresses +on a given port for a machine by specifying the IP address 0.0.0.0.
+Alternatively, the HTTP listener can specify a unique IP address for each listener, +but use the same port. +""" })} , {default_username, fun default_username/1} , {default_password, fun default_password/1} , {sample_interval, sc(emqx_schema:duration_s(), #{default => "10s"})} @@ -35,11 +45,23 @@ fields("dashboard") -> ]; fields("http") -> - [ {"protocol", hoconsc:enum([http, https])} - , {"port", hoconsc:mk(integer(), #{default => 18083})} - , {"num_acceptors", sc(integer(), #{default => 4})} + [ {"protocol", sc( + hoconsc:enum([http, https]), + #{ desc => "HTTP/HTTPS protocol." + , nullable => false + , default => http})} + , {"bind", fun bind/1} + , {"num_acceptors", sc( + integer(), + #{ default => 4 + , desc => "Socket acceptor pool for TCP protocols." + })} , {"max_connections", sc(integer(), #{default => 512})} - , {"backlog", sc(integer(), #{default => 1024})} + , {"backlog", sc( + integer(), + #{ default => 1024 + , desc => "Defines the maximum length that the queue of pending connections can grow to." + })} , {"send_timeout", sc(emqx_schema:duration(), #{default => "5s"})} , {"inet6", sc(boolean(), #{default => false})} , {"ipv6_v6only", sc(boolean(), #{default => false})} @@ -50,6 +72,12 @@ fields("https") -> proplists:delete("fail_if_no_peer_cert", emqx_schema:server_ssl_opts_schema(#{}, true)). +bind(type) -> hoconsc:union([non_neg_integer(), emqx_schema:ip_port()]); +bind(default) -> 18083; +bind(nullable) -> false; +bind(desc) -> "Port without IP(18083) or port with specified IP(127.0.0.1:18083)."; +bind(_) -> undefined. + default_username(type) -> string(); default_username(default) -> "admin"; default_username(nullable) -> false; @@ -67,6 +95,10 @@ default_password(_) -> undefined. cors(type) -> boolean(); cors(default) -> false; cors(nullable) -> true; +cors(desc) -> +"""Support Cross-Origin Resource Sharing (CORS). +Allows a server to indicate any origins (domain, scheme, or port) other than +its own from which a browser should permit loading resources."""; cors(_) -> undefined. sc(Type, Meta) -> hoconsc:mk(Type, Meta). diff --git a/apps/emqx_prometheus/rebar.config b/apps/emqx_prometheus/rebar.config index 1de169172..051dc4e4d 100644 --- a/apps/emqx_prometheus/rebar.config +++ b/apps/emqx_prometheus/rebar.config @@ -5,7 +5,7 @@ %% FIXME: tag this as v3.1.3 {prometheus, {git, "https://github.com/emqx/prometheus.erl", {ref, "9994c76adca40d91a2545102230ccce2423fd8a7"}}}, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.23.0"}}}, - {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.10"}}} + {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.11"}}} ]}. {edoc_opts, [{preprocess, true}]}. diff --git a/rebar.config b/rebar.config index 2a6d00696..b9b871724 100644 --- a/rebar.config +++ b/rebar.config @@ -56,7 +56,7 @@ , {mria, {git, "https://github.com/emqx/mria", {tag, "0.1.5"}}} , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.11.3"}}} , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.0"}}} - , {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.10"}}} + , {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.2.11"}}} , {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.2"}}} , {replayq, "0.3.3"} , {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}