feat(dashboard): support bind specific ip(port or ip:port).

This commit is contained in:
zhongwencool 2022-01-28 16:21:45 +08:00
parent 0cde9e6ecf
commit ec5d1b1463
6 changed files with 88 additions and 27 deletions

View File

@ -14,7 +14,7 @@ dashboard {
protocol = http protocol = http
num_acceptors = 4 num_acceptors = 4
max_connections = 512 max_connections = 512
port = 18083 bind = 18083
backlog = 512 backlog = 512
send_timeout = 5s send_timeout = 5s
inet6 = false inet6 = false
@ -23,7 +23,7 @@ dashboard {
# , # ,
# { # {
# protocol = https # protocol = https
# port = 18084 # bind = "127.0.0.1:18084"
# num_acceptors = 2 # num_acceptors = 2
# backlog = 512 # backlog = 512
# send_timeout = 5s # send_timeout = 5s

View File

@ -61,15 +61,26 @@ start_listeners() ->
dispatch => Dispatch, dispatch => Dispatch,
middlewares => [cowboy_router, ?EMQX_MIDDLE, cowboy_handler] middlewares => [cowboy_router, ?EMQX_MIDDLE, cowboy_handler]
}, },
[begin Res =
Minirest = maps:put(protocol, Protocol, BaseMinirest), lists:foldl(fun({Name, Protocol, Port, RanchOptions}, Acc) ->
{ok, _} = minirest:start(Name, RanchOptions, Minirest), Minirest = BaseMinirest#{protocol => Protocol},
?ULOG("Start listener ~ts on ~p successfully.~n", [Name, Port]) case minirest:start(Name, RanchOptions, Minirest) of
end || {Name, Protocol, Port, RanchOptions} <- listeners()]. {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() -> stop_listeners() ->
[begin [begin
ok = minirest:stop(Name), _ = minirest:stop(Name),
?ULOG("Stop listener ~ts on ~p successfully.~n", [Name, Port]) ?ULOG("Stop listener ~ts on ~p successfully.~n", [Name, Port])
end || {Name, _, Port, _} <- listeners()]. end || {Name, _, Port, _} <- listeners()].
@ -85,12 +96,19 @@ apps() ->
listeners() -> listeners() ->
[begin [begin
Protocol = maps:get(protocol, ListenerOptions, http), Protocol = maps:get(protocol, ListenerOption0, http),
Port = maps:get(port, ListenerOptions, 18083), {ListenerOption, Port} = ip_port(ListenerOption0),
Name = listener_name(Protocol, Port), Name = listener_name(Protocol, ListenerOption),
RanchOptions = ranch_opts(maps:without([protocol], ListenerOptions)), RanchOptions = ranch_opts(maps:without([protocol], ListenerOption)),
{Name, Protocol, Port, RanchOptions} {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) -> ranch_opts(RanchOptions) ->
Keys = [ {ack_timeout, handshake_timeout} 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, false, S) -> S;
key_only(K , V , S) -> [{K, V} | S]. key_only(K , V , S) -> [{K, V} | S].
listener_name(Protocol, Port) -> listener_name(Protocol, #{port := Port, ip := IP}) ->
Name = "dashboard:" ++ atom_to_list(Protocol) ++ ":" ++ integer_to_list(Port), 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). list_to_atom(Name).
authorize(Req) -> authorize(Req) ->

View File

@ -27,10 +27,13 @@
start(_StartType, _StartArgs) -> start(_StartType, _StartArgs) ->
{ok, Sup} = emqx_dashboard_sup:start_link(), {ok, Sup} = emqx_dashboard_sup:start_link(),
ok = mria_rlog:wait_for_shards([?DASHBOARD_SHARD], infinity), ok = mria_rlog:wait_for_shards([?DASHBOARD_SHARD], infinity),
_ = emqx_dashboard:start_listeners(), case emqx_dashboard:start_listeners() of
emqx_dashboard_cli:load(), ok ->
{ok, _Result} = emqx_dashboard_admin:add_default_user(), emqx_dashboard_cli:load(),
{ok, Sup}. {ok, _Result} = emqx_dashboard_admin:add_default_user(),
{ok, Sup};
{error, Reason} -> {error, Reason}
end.
stop(_State) -> stop(_State) ->
emqx_dashboard_cli:unload(), emqx_dashboard_cli:unload(),

View File

@ -25,8 +25,18 @@ namespace() -> <<"dashboard">>.
roots() -> ["dashboard"]. roots() -> ["dashboard"].
fields("dashboard") -> fields("dashboard") ->
[ {listeners, hoconsc:array(hoconsc:union([hoconsc:ref(?MODULE, "http"), [ {listeners,
hoconsc:ref(?MODULE, "https")]))} 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.<br>
Listeners must have a unique combination of port number and IP address.<br>
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.<br>
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_username, fun default_username/1}
, {default_password, fun default_password/1} , {default_password, fun default_password/1}
, {sample_interval, sc(emqx_schema:duration_s(), #{default => "10s"})} , {sample_interval, sc(emqx_schema:duration_s(), #{default => "10s"})}
@ -35,11 +45,23 @@ fields("dashboard") ->
]; ];
fields("http") -> fields("http") ->
[ {"protocol", hoconsc:enum([http, https])} [ {"protocol", sc(
, {"port", hoconsc:mk(integer(), #{default => 18083})} hoconsc:enum([http, https]),
, {"num_acceptors", sc(integer(), #{default => 4})} #{ 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})} , {"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"})} , {"send_timeout", sc(emqx_schema:duration(), #{default => "5s"})}
, {"inet6", sc(boolean(), #{default => false})} , {"inet6", sc(boolean(), #{default => false})}
, {"ipv6_v6only", sc(boolean(), #{default => false})} , {"ipv6_v6only", sc(boolean(), #{default => false})}
@ -50,6 +72,12 @@ fields("https") ->
proplists:delete("fail_if_no_peer_cert", proplists:delete("fail_if_no_peer_cert",
emqx_schema:server_ssl_opts_schema(#{}, true)). 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(type) -> string();
default_username(default) -> "admin"; default_username(default) -> "admin";
default_username(nullable) -> false; default_username(nullable) -> false;
@ -67,6 +95,10 @@ default_password(_) -> undefined.
cors(type) -> boolean(); cors(type) -> boolean();
cors(default) -> false; cors(default) -> false;
cors(nullable) -> true; 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. cors(_) -> undefined.
sc(Type, Meta) -> hoconsc:mk(Type, Meta). sc(Type, Meta) -> hoconsc:mk(Type, Meta).

View File

@ -5,7 +5,7 @@
%% FIXME: tag this as v3.1.3 %% FIXME: tag this as v3.1.3
{prometheus, {git, "https://github.com/emqx/prometheus.erl", {ref, "9994c76adca40d91a2545102230ccce2423fd8a7"}}}, {prometheus, {git, "https://github.com/emqx/prometheus.erl", {ref, "9994c76adca40d91a2545102230ccce2423fd8a7"}}},
{hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.23.0"}}}, {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}]}. {edoc_opts, [{preprocess, true}]}.

View File

@ -56,7 +56,7 @@
, {mria, {git, "https://github.com/emqx/mria", {tag, "0.1.5"}}} , {mria, {git, "https://github.com/emqx/mria", {tag, "0.1.5"}}}
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.11.3"}}} , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.11.3"}}}
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.0"}}} , {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"}}} , {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.2"}}}
, {replayq, "0.3.3"} , {replayq, "0.3.3"}
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}} , {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}