feat: add a json format support for the /status API
This commit is contained in:
parent
9260b5ec6c
commit
ed7a8659d2
|
@ -45,6 +45,17 @@ schema("/status") ->
|
||||||
#{
|
#{
|
||||||
'operationId' => get_status,
|
'operationId' => get_status,
|
||||||
get => #{
|
get => #{
|
||||||
|
parameters => [
|
||||||
|
{format,
|
||||||
|
hoconsc:mk(
|
||||||
|
string(),
|
||||||
|
#{
|
||||||
|
in => query,
|
||||||
|
default => <<"text">>,
|
||||||
|
desc => ?DESC(get_status_api_format)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
],
|
||||||
description => ?DESC(get_status_api),
|
description => ?DESC(get_status_api),
|
||||||
tags => ?TAGS,
|
tags => ?TAGS,
|
||||||
security => [],
|
security => [],
|
||||||
|
@ -70,7 +81,16 @@ path() ->
|
||||||
"/status".
|
"/status".
|
||||||
|
|
||||||
init(Req0, State) ->
|
init(Req0, State) ->
|
||||||
{Code, Headers, Body} = running_status(),
|
Format =
|
||||||
|
try
|
||||||
|
QS = cowboy_req:parse_qs(Req0),
|
||||||
|
{_, F} = lists:keyfind(<<"format">>, 1, QS),
|
||||||
|
F
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
<<"text">>
|
||||||
|
end,
|
||||||
|
{Code, Headers, Body} = running_status(Format),
|
||||||
Req = cowboy_req:reply(Code, Headers, Body, Req0),
|
Req = cowboy_req:reply(Code, Headers, Body, Req0),
|
||||||
{ok, Req, State}.
|
{ok, Req, State}.
|
||||||
|
|
||||||
|
@ -78,29 +98,52 @@ init(Req0, State) ->
|
||||||
%% API Handler funcs
|
%% API Handler funcs
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
get_status(get, _Params) ->
|
get_status(get, Params) ->
|
||||||
running_status().
|
Format = maps:get(<<"format">>, maps:get(query_string, Params, #{}), <<"text">>),
|
||||||
|
running_status(iolist_to_binary(Format)).
|
||||||
|
|
||||||
running_status() ->
|
running_status(Format) ->
|
||||||
case emqx_dashboard_listener:is_ready(timer:seconds(20)) of
|
case emqx_dashboard_listener:is_ready(timer:seconds(20)) of
|
||||||
true ->
|
true ->
|
||||||
BrokerStatus = broker_status(),
|
|
||||||
AppStatus = application_status(),
|
AppStatus = application_status(),
|
||||||
Body = io_lib:format("Node ~ts is ~ts~nemqx is ~ts", [node(), BrokerStatus, AppStatus]),
|
Body = do_get_status(AppStatus, Format),
|
||||||
StatusCode =
|
StatusCode =
|
||||||
case AppStatus of
|
case AppStatus of
|
||||||
running -> 200;
|
running -> 200;
|
||||||
not_running -> 503
|
not_running -> 503
|
||||||
end,
|
end,
|
||||||
|
ContentType =
|
||||||
|
case Format of
|
||||||
|
<<"json">> -> <<"applicatin/json">>;
|
||||||
|
_ -> <<"text/plain">>
|
||||||
|
end,
|
||||||
Headers = #{
|
Headers = #{
|
||||||
<<"content-type">> => <<"text/plain">>,
|
<<"content-type">> => ContentType,
|
||||||
<<"retry-after">> => <<"15">>
|
<<"retry-after">> => <<"15">>
|
||||||
},
|
},
|
||||||
{StatusCode, Headers, list_to_binary(Body)};
|
{StatusCode, Headers, iolist_to_binary(Body)};
|
||||||
false ->
|
false ->
|
||||||
{503, #{<<"retry-after">> => <<"15">>}, <<>>}
|
{503, #{<<"retry-after">> => <<"15">>}, <<>>}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
do_get_status(AppStatus, <<"json">>) ->
|
||||||
|
BrokerStatus = broker_status(),
|
||||||
|
emqx_utils_json:encode(#{
|
||||||
|
node_name => atom_to_binary(node(), utf8),
|
||||||
|
rel_vsn => vsn(),
|
||||||
|
broker_status => atom_to_binary(BrokerStatus),
|
||||||
|
app_status => atom_to_binary(AppStatus)
|
||||||
|
});
|
||||||
|
do_get_status(AppStatus, _) ->
|
||||||
|
BrokerStatus = broker_status(),
|
||||||
|
io_lib:format("Node ~ts is ~ts~nemqx is ~ts", [node(), BrokerStatus, AppStatus]).
|
||||||
|
|
||||||
|
vsn() ->
|
||||||
|
iolist_to_binary([
|
||||||
|
emqx_release:edition_vsn_prefix(),
|
||||||
|
emqx_release:version()
|
||||||
|
]).
|
||||||
|
|
||||||
broker_status() ->
|
broker_status() ->
|
||||||
case emqx:is_running() of
|
case emqx:is_running() of
|
||||||
true ->
|
true ->
|
||||||
|
|
|
@ -38,7 +38,10 @@ all() ->
|
||||||
get_status_tests() ->
|
get_status_tests() ->
|
||||||
[
|
[
|
||||||
t_status_ok,
|
t_status_ok,
|
||||||
t_status_not_ok
|
t_status_not_ok,
|
||||||
|
t_status_text_format,
|
||||||
|
t_status_json_format,
|
||||||
|
t_status_bad_format_qs
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
|
@ -87,8 +90,10 @@ do_request(Opts) ->
|
||||||
headers := Headers,
|
headers := Headers,
|
||||||
body := Body0
|
body := Body0
|
||||||
} = Opts,
|
} = Opts,
|
||||||
|
QS = maps:get(qs, Opts, ""),
|
||||||
URL = ?HOST ++ filename:join(Path0),
|
URL = ?HOST ++ filename:join(Path0),
|
||||||
{ok, #{host := Host, port := Port, path := Path}} = emqx_http_lib:uri_parse(URL),
|
{ok, #{host := Host, port := Port, path := Path1}} = emqx_http_lib:uri_parse(URL),
|
||||||
|
Path = Path1 ++ QS,
|
||||||
%% we must not use `httpc' here, because it keeps retrying when it
|
%% we must not use `httpc' here, because it keeps retrying when it
|
||||||
%% receives a 503 with `retry-after' header, and there's no option
|
%% receives a 503 with `retry-after' header, and there's no option
|
||||||
%% to stop that behavior...
|
%% to stop that behavior...
|
||||||
|
@ -165,3 +170,73 @@ t_status_not_ok(Config) ->
|
||||||
Headers
|
Headers
|
||||||
),
|
),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_status_text_format(Config) ->
|
||||||
|
Path = ?config(get_status_path, Config),
|
||||||
|
#{
|
||||||
|
body := Resp,
|
||||||
|
status_code := StatusCode
|
||||||
|
} = do_request(#{
|
||||||
|
method => get,
|
||||||
|
path => Path,
|
||||||
|
qs => "?format=text",
|
||||||
|
headers => [],
|
||||||
|
body => no_body
|
||||||
|
}),
|
||||||
|
?assertEqual(200, StatusCode),
|
||||||
|
?assertMatch(
|
||||||
|
{match, _},
|
||||||
|
re:run(Resp, <<"emqx is running$">>)
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_status_json_format(Config) ->
|
||||||
|
Path = ?config(get_status_path, Config),
|
||||||
|
#{
|
||||||
|
body := Resp,
|
||||||
|
status_code := StatusCode
|
||||||
|
} = do_request(#{
|
||||||
|
method => get,
|
||||||
|
path => Path,
|
||||||
|
qs => "?format=json",
|
||||||
|
headers => [],
|
||||||
|
body => no_body
|
||||||
|
}),
|
||||||
|
?assertEqual(200, StatusCode),
|
||||||
|
?assertMatch(
|
||||||
|
#{<<"app_status">> := <<"running">>},
|
||||||
|
emqx_utils_json:decode(Resp)
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_status_bad_format_qs(Config) ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(QS) ->
|
||||||
|
test_status_bad_format_qs(QS, Config)
|
||||||
|
end,
|
||||||
|
[
|
||||||
|
"?a=b",
|
||||||
|
"?format=",
|
||||||
|
"?format=x"
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
|
%% when query-sting is invalid, fallback to text format
|
||||||
|
test_status_bad_format_qs(QS, Config) ->
|
||||||
|
Path = ?config(get_status_path, Config),
|
||||||
|
#{
|
||||||
|
body := Resp,
|
||||||
|
status_code := StatusCode
|
||||||
|
} = do_request(#{
|
||||||
|
method => get,
|
||||||
|
path => Path,
|
||||||
|
qs => QS,
|
||||||
|
headers => [],
|
||||||
|
body => no_body
|
||||||
|
}),
|
||||||
|
?assertEqual(200, StatusCode),
|
||||||
|
?assertMatch(
|
||||||
|
{match, _},
|
||||||
|
re:run(Resp, <<"emqx is running$">>)
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
|
@ -1,21 +1,42 @@
|
||||||
emqx_mgmt_api_status {
|
emqx_mgmt_api_status {
|
||||||
|
|
||||||
get_status_api.desc:
|
get_status_api.desc:
|
||||||
"""Serves as a health check for the node. Returns a plain text response describing the status of the node. This endpoint requires no authentication.
|
"""Serves as a health check for the node.
|
||||||
|
Returns response to describe the status of the node and the application.
|
||||||
|
|
||||||
|
This endpoint requires no authentication.
|
||||||
|
|
||||||
Returns status code 200 if the EMQX application is up and running, 503 otherwise.
|
Returns status code 200 if the EMQX application is up and running, 503 otherwise.
|
||||||
This API was introduced in v5.0.10.
|
This API was introduced in v5.0.10.
|
||||||
The GET `/status` endpoint (without the `/api/...` prefix) is also an alias to this endpoint and works in the same way. This alias has been available since v5.0.0."""
|
The GET `/status` endpoint (without the `/api/...` prefix) is also an alias to this endpoint and works in the same way.
|
||||||
|
This alias has been available since v5.0.0.
|
||||||
|
|
||||||
|
Starting from v5.0.25 or e5.0.4, you can also use 'format' parameter to get JSON format information.
|
||||||
|
"""
|
||||||
|
|
||||||
get_status_api.label:
|
get_status_api.label:
|
||||||
"""Service health check"""
|
"""Service health check"""
|
||||||
|
|
||||||
get_status_response200.desc:
|
get_status_response200.desc:
|
||||||
"""Node emqx@127.0.0.1 is started
|
"""If 'format' parameter is 'json', then it returns a JSON like below:<br/>
|
||||||
|
{
|
||||||
|
"rel_vsn": "v5.0.23",
|
||||||
|
"node_name": "emqx@127.0.0.1",
|
||||||
|
"broker_status": "started",
|
||||||
|
"app_status": "running"
|
||||||
|
}
|
||||||
|
<br/>
|
||||||
|
Otherwise it returns free text strings as below:<br/>
|
||||||
|
Node emqx@127.0.0.1 is started
|
||||||
emqx is running"""
|
emqx is running"""
|
||||||
|
|
||||||
get_status_response503.desc:
|
get_status_response503.desc:
|
||||||
"""Node emqx@127.0.0.1 is stopped
|
"""When EMQX application is temporary not running or being restarted, it may return 'emqx is not_running'.
|
||||||
emqx is not_running"""
|
If the 'format' parameter is provided 'json', the nthe 'app_status' field in the JSON object is 'not_running'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
get_status_api_format.desc:
|
||||||
|
"""Specify the response format, 'text' (default) to return the HTTP body in free text,
|
||||||
|
or 'json' to return the HTTP body with a JSON object."""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,34 @@
|
||||||
emqx_mgmt_api_status {
|
emqx_mgmt_api_status {
|
||||||
|
|
||||||
get_status_api.desc:
|
get_status_api.desc:
|
||||||
"""作为节点的健康检查。 返回一个纯文本的响应,描述节点的状态。
|
"""节点的健康检查。 返回节点状态的描述信息。
|
||||||
|
|
||||||
如果 EMQX 应用程序已经启动并运行,返回状态代码 200,否则返回 503。
|
如果 EMQX 应用程序已经启动并运行,返回状态代码 200,否则返回 503。
|
||||||
|
|
||||||
这个API是在v5.0.10中引入的。
|
这个API是在v5.0.10中引入的。
|
||||||
GET `/status`端点(没有`/api/...`前缀)也是这个端点的一个别名,工作方式相同。 这个别名从v5.0.0开始就有了。"""
|
GET `/status`端点(没有`/api/...`前缀)也是这个端点的一个别名,工作方式相同。 这个别名从v5.0.0开始就有了。
|
||||||
|
自 v5.0.25 和 e5.0.4 开始,可以通过指定 'format' 参数来得到 JSON 格式的信息。"""
|
||||||
|
|
||||||
get_status_api.label:
|
get_status_api.label:
|
||||||
"""服务健康检查"""
|
"""服务健康检查"""
|
||||||
|
|
||||||
get_status_response200.desc:
|
get_status_response200.desc:
|
||||||
"""Node emqx@127.0.0.1 is started
|
"""如果 'format' 参数为 'json',则返回如下JSON:<br/>
|
||||||
|
{
|
||||||
|
"rel_vsn": "v5.0.23",
|
||||||
|
"node_name": "emqx@127.0.0.1",
|
||||||
|
"broker_status": "started",
|
||||||
|
"app_status": "running"
|
||||||
|
}
|
||||||
|
<br/>
|
||||||
|
否则返回2行自由格式的文本,第一行描述节点的状态,第二行描述 EMQX 应用运行状态。例如:<br/>
|
||||||
|
Node emqx@127.0.0.1 is started
|
||||||
emqx is running"""
|
emqx is running"""
|
||||||
|
|
||||||
get_status_response503.desc:
|
get_status_response503.desc:
|
||||||
"""Node emqx@127.0.0.1 is stopped
|
"""如果 EMQX 应用暂时没有启动,或正在重启,则可能返回 'emqx is not_running'"""
|
||||||
emqx is not_running"""
|
|
||||||
|
get_status_api_format.desc:
|
||||||
|
"""指定返回的内容格式。使用 'text'(默认)则返回自由格式的字符串; 'json' 则返回 JSON 格式。"""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue