Просмотр исходного кода

Merge pull request #6890 from zhongwencool/dashboard-bind-support-ip

feat(dashboard): support bind specific ip(port or ip:port).
zhongwencool 4 лет назад
Родитель
Сommit
92cbf86951

+ 2 - 2
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

+ 45 - 15
apps/emqx_dashboard/src/emqx_dashboard.erl

@@ -61,17 +61,32 @@ 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),
-        ?ULOG("Stop listener ~ts on ~p successfully.~n", [Name, Port])
-    end || {Name, _, Port, _} <- listeners()].
+        case minirest:stop(Name) of
+            ok ->
+                ?ULOG("Stop listener ~ts on ~p successfully.~n", [Name, Port]);
+            {error, not_found} ->
+                ?SLOG(warning, #{msg => "stop_listener_failed", name => Name, port => Port})
+        end
+     end || {Name, _, Port, _} <- listeners()].
 
 %%--------------------------------------------------------------------
 %% internal
@@ -85,12 +100,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 +141,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) ->

+ 7 - 4
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(),

+ 38 - 6
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 are identified by their protocol type and are
+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_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).

+ 1 - 1
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}]}.

+ 1 - 1
mix.exs

@@ -48,7 +48,7 @@ defmodule EMQXUmbrella.MixProject do
       {:mria, github: "emqx/mria", tag: "0.1.5", override: true},
       {:ekka, github: "emqx/ekka", tag: "0.11.3", override: true},
       {:gen_rpc, github: "emqx/gen_rpc", tag: "2.8.0", override: true},
-      {:minirest, github: "emqx/minirest", tag: "1.2.10", override: true},
+      {:minirest, github: "emqx/minirest", tag: "1.2.11", override: true},
       {:ecpool, github: "emqx/ecpool", tag: "0.5.2"},
       {:replayq, "0.3.3", override: true},
       {:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},

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