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"}}}