fix
This commit is contained in:
parent
811029ddb5
commit
f0facb3691
@ -11,9 +11,9 @@
|
|||||||
-include("endpoint.hrl").
|
-include("endpoint.hrl").
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start_test/0, test_consumer/0]).
|
-export([start_manual/0, test_consumer/0]).
|
||||||
|
|
||||||
start_test() ->
|
start_manual() ->
|
||||||
Name = endpoint:get_name(100),
|
Name = endpoint:get_name(100),
|
||||||
{ok, Pid} = endpoint_kafka:start_link(Name, #endpoint{
|
{ok, Pid} = endpoint_kafka:start_link(Name, #endpoint{
|
||||||
id = 100,
|
id = 100,
|
||||||
|
|||||||
@ -13,13 +13,5 @@
|
|||||||
-export([test/0]).
|
-export([test/0]).
|
||||||
|
|
||||||
test() ->
|
test() ->
|
||||||
|
{error, disabled}.
|
||||||
{ok, Content} = file:read_file("/tmp/test.erl"),
|
|
||||||
|
|
||||||
{ok, Tokens, _} = erl_scan:string(binary_to_list(Content)),
|
|
||||||
{ok, ExprList} = erl_parse:parse_exprs(Tokens),
|
|
||||||
|
|
||||||
{value, F, _NewBindings} = erl_eval:exprs(ExprList, []),
|
|
||||||
F(#{name => <<"test">>}).
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -31,17 +31,17 @@ start_link(Endpoint = #endpoint{id = Id, config = #kafka_endpoint{}}) ->
|
|||||||
LocalName = get_name(Id),
|
LocalName = get_name(Id),
|
||||||
endpoint_kafka:start_link(LocalName, Endpoint).
|
endpoint_kafka:start_link(LocalName, Endpoint).
|
||||||
|
|
||||||
-spec get_name(Id :: integer()) -> atom().
|
-spec get_name(Id :: integer()) -> term().
|
||||||
get_name(Id) when is_integer(Id) ->
|
get_name(Id) when is_integer(Id) ->
|
||||||
list_to_atom("endpoint:" ++ integer_to_list(Id)).
|
{endpoint, Id}.
|
||||||
|
|
||||||
-spec get_pid(Id :: integer()) -> undefined | pid().
|
-spec get_pid(Id :: integer()) -> undefined | pid().
|
||||||
get_pid(Id) when is_integer(Id) ->
|
get_pid(Id) when is_integer(Id) ->
|
||||||
whereis(get_name(Id)).
|
gproc:whereis_name({n, l, get_name(Id)}).
|
||||||
|
|
||||||
-spec get_alias_name(Name :: binary()) -> atom().
|
-spec get_alias_name(Name :: binary()) -> term().
|
||||||
get_alias_name(Name) when is_binary(Name) ->
|
get_alias_name(Name) when is_binary(Name) ->
|
||||||
list_to_atom("endpoint:" ++ binary_to_list(Name)).
|
{endpoint_alias, Name}.
|
||||||
|
|
||||||
-spec get_alias_pid(Name :: binary()) -> undefined | pid().
|
-spec get_alias_pid(Name :: binary()) -> undefined | pid().
|
||||||
get_alias_pid(Name) when is_binary(Name) ->
|
get_alias_pid(Name) when is_binary(Name) ->
|
||||||
@ -92,16 +92,27 @@ endpoint_record(#{<<"id">> := Id, <<"matcher">> := Matcher, <<"title">> := Title
|
|||||||
|
|
||||||
-spec parse_config(Protocol :: binary(), Config :: map()) -> {ok, #mqtt_endpoint{} | #kafka_endpoint{} | #http_endpoint{}} | {error, Errors :: [Error :: binary()]}.
|
-spec parse_config(Protocol :: binary(), Config :: map()) -> {ok, #mqtt_endpoint{} | #kafka_endpoint{} | #http_endpoint{}} | {error, Errors :: [Error :: binary()]}.
|
||||||
parse_config(<<"mqtt">>, #{<<"host">> := Host, <<"port">> := Port0, <<"client_id">> := ClientId, <<"username">> := Username, <<"password">> := Password, <<"topic">> := Topic, <<"qos">> := Qos}) ->
|
parse_config(<<"mqtt">>, #{<<"host">> := Host, <<"port">> := Port0, <<"client_id">> := ClientId, <<"username">> := Username, <<"password">> := Password, <<"topic">> := Topic, <<"qos">> := Qos}) ->
|
||||||
Port = if is_binary(Port0) -> binary_to_integer(Port0); is_integer(Port0) -> Port0 end,
|
{Port, PortErrors} = case parse_integer(Port0) of
|
||||||
|
{ok, ParsedPort} ->
|
||||||
|
{ParsedPort, []};
|
||||||
|
error ->
|
||||||
|
{undefined, [<<"port invalid">>]}
|
||||||
|
end,
|
||||||
|
|
||||||
Errors = lists:filtermap(fun(Term) ->
|
CheckTerms = case Port of
|
||||||
|
undefined ->
|
||||||
|
[{host, Host}, {username, Username}, {password, Password}, {topic, Topic}, {qos, Qos}];
|
||||||
|
_ ->
|
||||||
|
[{host, Host}, {port, Port}, {username, Username}, {password, Password}, {topic, Topic}, {qos, Qos}]
|
||||||
|
end,
|
||||||
|
Errors = PortErrors ++ lists:filtermap(fun(Term) ->
|
||||||
case check_mqtt_argument(Term) of
|
case check_mqtt_argument(Term) of
|
||||||
ok ->
|
ok ->
|
||||||
false;
|
false;
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
{true, Error}
|
{true, Error}
|
||||||
end
|
end
|
||||||
end, [{host, Host}, {port, Port}, {username, Username}, {password, Password}, {topic, Topic}, {qos, Qos}]),
|
end, CheckTerms),
|
||||||
case Errors =:= [] of
|
case Errors =:= [] of
|
||||||
true ->
|
true ->
|
||||||
{ok, #mqtt_endpoint{
|
{ok, #mqtt_endpoint{
|
||||||
@ -181,8 +192,8 @@ parse_config(_, _) ->
|
|||||||
-spec parse_kafka_bootstrap_servers(BootstrapServers :: [binary()]) -> Servers :: [{Host :: string(), Port :: integer()}].
|
-spec parse_kafka_bootstrap_servers(BootstrapServers :: [binary()]) -> Servers :: [{Host :: string(), Port :: integer()}].
|
||||||
parse_kafka_bootstrap_servers(BootstrapServers) when is_list(BootstrapServers) ->
|
parse_kafka_bootstrap_servers(BootstrapServers) when is_list(BootstrapServers) ->
|
||||||
lists:map(fun(S) ->
|
lists:map(fun(S) ->
|
||||||
[Host0, Port0] = binary:split(S, <<":">>),
|
{ok, Host, Port} = parse_kafka_bootstrap_server(S),
|
||||||
{binary_to_list(Host0), binary_to_integer(Port0)}
|
{Host, Port}
|
||||||
end, BootstrapServers).
|
end, BootstrapServers).
|
||||||
|
|
||||||
-spec parse_kafka_mechanism(Mechanism0 :: binary()) -> atom().
|
-spec parse_kafka_mechanism(Mechanism0 :: binary()) -> atom().
|
||||||
@ -231,17 +242,10 @@ check_kafka_argument({bootstrap_servers, BootstrapServers}) ->
|
|||||||
case is_list(BootstrapServers) andalso length(BootstrapServers) > 0 of
|
case is_list(BootstrapServers) andalso length(BootstrapServers) > 0 of
|
||||||
true ->
|
true ->
|
||||||
InvalidServers = lists:filtermap(fun(S) ->
|
InvalidServers = lists:filtermap(fun(S) ->
|
||||||
case binary:split(S, <<":">>) of
|
case parse_kafka_bootstrap_server(S) of
|
||||||
[Host0, Port0] ->
|
{ok, _Host, _Port} ->
|
||||||
Host = binary_to_list(Host0),
|
false;
|
||||||
Port = binary_to_integer(Port0),
|
error ->
|
||||||
case not string:is_empty(Host) andalso (is_integer(Port) andalso Port > 0) of
|
|
||||||
true ->
|
|
||||||
false;
|
|
||||||
false ->
|
|
||||||
{true, S}
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
{true, S}
|
{true, S}
|
||||||
end
|
end
|
||||||
end, BootstrapServers),
|
end, BootstrapServers),
|
||||||
@ -322,3 +326,34 @@ check_mqtt_argument({qos, Qos}) ->
|
|||||||
false ->
|
false ->
|
||||||
{error, <<"qos invalid">>}
|
{error, <<"qos invalid">>}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec parse_integer(term()) -> {ok, integer()} | error.
|
||||||
|
parse_integer(Value) when is_integer(Value) ->
|
||||||
|
{ok, Value};
|
||||||
|
parse_integer(Value) when is_binary(Value) ->
|
||||||
|
try binary_to_integer(Value) of
|
||||||
|
Int ->
|
||||||
|
{ok, Int}
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
error
|
||||||
|
end;
|
||||||
|
parse_integer(_) ->
|
||||||
|
error.
|
||||||
|
|
||||||
|
-spec parse_kafka_bootstrap_server(term()) -> {ok, string(), integer()} | error.
|
||||||
|
parse_kafka_bootstrap_server(Server) when is_binary(Server) ->
|
||||||
|
case binary:split(Server, <<":">>) of
|
||||||
|
[Host0, Port0] ->
|
||||||
|
Host = binary_to_list(Host0),
|
||||||
|
case parse_integer(Port0) of
|
||||||
|
{ok, Port} when Host =/= [], Port > 0 ->
|
||||||
|
{ok, Host, Port};
|
||||||
|
_ ->
|
||||||
|
error
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
error
|
||||||
|
end;
|
||||||
|
parse_kafka_bootstrap_server(_) ->
|
||||||
|
error.
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
-define(RETRY_INTERVAL, 5000).
|
-define(RETRY_INTERVAL, 5000).
|
||||||
%% 最大重试次数,不包含首次发送
|
%% 最大重试次数,不包含首次发送
|
||||||
-define(MAX_RETRY_TIMES, 3).
|
-define(MAX_RETRY_TIMES, 3).
|
||||||
%% 与 endpoint_outbox 的单条记录限制保持一致;fast path 也不能绕过该限制。
|
%% 与 endpoint_outbox 的单条记录限制保持一致。
|
||||||
-define(MAX_PAYLOAD_BYTES, 32 * 1024).
|
-define(MAX_PAYLOAD_BYTES, 32 * 1024).
|
||||||
|
|
||||||
-export([new/2, append/2, append_only/2, trigger_next/1, trigger_n/1, handle_timeout/4, cleanup/1, recover_inflight/1, ack/2, stat/1, resize/2]).
|
-export([new/2, append/2, append_only/2, trigger_next/1, trigger_n/1, handle_timeout/4, cleanup/1, recover_inflight/1, ack/2, stat/1, resize/2]).
|
||||||
@ -29,8 +29,6 @@
|
|||||||
outbox :: endpoint_outbox:outbox(),
|
outbox :: endpoint_outbox:outbox(),
|
||||||
%% 当前待 ack 的数据及其重试定时器 #{Id => {TimerRef, Payload, RetryTimes, Source}}
|
%% 当前待 ack 的数据及其重试定时器 #{Id => {TimerRef, Payload, RetryTimes, Source}}
|
||||||
timer_map = #{} :: #{integer() => timer_entry()},
|
timer_map = #{} :: #{integer() => timer_entry()},
|
||||||
%% 内存 fast path 使用负数 id,避免与 outbox 的正整数 seq 冲突。
|
|
||||||
next_memory_id = -1 :: integer(),
|
|
||||||
%% 窗口大小,允许最大的未确认消息数
|
%% 窗口大小,允许最大的未确认消息数
|
||||||
window_size = 10,
|
window_size = 10,
|
||||||
%% 未确认的消息数
|
%% 未确认的消息数
|
||||||
@ -52,21 +50,17 @@ new(Endpoint = #endpoint{id = Id}, WindowSize) when is_integer(WindowSize), Wind
|
|||||||
#buffer{outbox = Outbox, endpoint = Endpoint, window_size = WindowSize}.
|
#buffer{outbox = Outbox, endpoint = Endpoint, window_size = WindowSize}.
|
||||||
|
|
||||||
-spec append(Payload :: binary(), Buffer :: #buffer{}) -> NBuffer :: #buffer{}.
|
-spec append(Payload :: binary(), Buffer :: #buffer{}) -> NBuffer :: #buffer{}.
|
||||||
append(Payload, Buffer = #buffer{outbox = Outbox, window_size = WindowSize, flight_num = FlightNum}) when is_binary(Payload) ->
|
append(Payload, Buffer = #buffer{}) when is_binary(Payload) ->
|
||||||
case validate_payload_size(Payload, Buffer) of
|
case validate_payload_size(Payload, Buffer) of
|
||||||
ok ->
|
ok ->
|
||||||
case FlightNum < WindowSize andalso outbox_empty(Outbox) of
|
%% Persist before dispatch so a VM crash does not lose in-flight data.
|
||||||
true ->
|
case append_to_outbox(Payload, Buffer) of
|
||||||
dispatch_memory(Payload, Buffer);
|
{ok, NBuffer} ->
|
||||||
false ->
|
trigger_next(NBuffer);
|
||||||
case append_to_outbox(Payload, Buffer) of
|
{dropped, NBuffer} ->
|
||||||
{ok, NBuffer} ->
|
trigger_next(NBuffer);
|
||||||
trigger_next(NBuffer);
|
{error, NBuffer} ->
|
||||||
{dropped, NBuffer} ->
|
NBuffer
|
||||||
trigger_next(NBuffer);
|
|
||||||
{error, NBuffer} ->
|
|
||||||
NBuffer
|
|
||||||
end
|
|
||||||
end;
|
end;
|
||||||
error ->
|
error ->
|
||||||
Buffer
|
Buffer
|
||||||
@ -228,11 +222,6 @@ validate_payload_size(Payload, Buffer) ->
|
|||||||
error
|
error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec outbox_empty(endpoint_outbox:outbox()) -> boolean().
|
|
||||||
outbox_empty(Outbox) ->
|
|
||||||
Stat = endpoint_outbox:stat(Outbox),
|
|
||||||
maps:get(write_seq, Stat, 0) =:= maps:get(acked_seq, Stat, 0).
|
|
||||||
|
|
||||||
-spec append_to_outbox(binary(), buffer()) -> {ok | dropped | error, buffer()}.
|
-spec append_to_outbox(binary(), buffer()) -> {ok | dropped | error, buffer()}.
|
||||||
append_to_outbox(Payload, Buffer = #buffer{outbox = Outbox}) ->
|
append_to_outbox(Payload, Buffer = #buffer{outbox = Outbox}) ->
|
||||||
case endpoint_outbox:append(Payload, Outbox) of
|
case endpoint_outbox:append(Payload, Outbox) of
|
||||||
@ -247,15 +236,6 @@ append_to_outbox(Payload, Buffer = #buffer{outbox = Outbox}) ->
|
|||||||
{error, Buffer}
|
{error, Buffer}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec dispatch_memory(binary(), buffer()) -> buffer().
|
|
||||||
dispatch_memory(Payload, Buffer = #buffer{next_memory_id = Id, flight_num = FlightNum}) ->
|
|
||||||
ReceiverPid = self(),
|
|
||||||
ReceiverPid ! {next_data, Id, Payload},
|
|
||||||
schedule_retry(Id, Payload, 0, memory, Buffer#buffer{
|
|
||||||
next_memory_id = Id - 1,
|
|
||||||
flight_num = FlightNum + 1
|
|
||||||
}).
|
|
||||||
|
|
||||||
-spec schedule_retry(integer(), binary(), non_neg_integer(), flight_source(), buffer()) -> buffer().
|
-spec schedule_retry(integer(), binary(), non_neg_integer(), flight_source(), buffer()) -> buffer().
|
||||||
schedule_retry(Id, Payload, RetryTimes, Source, Buffer = #buffer{timer_map = TimerMap})
|
schedule_retry(Id, Payload, RetryTimes, Source, Buffer = #buffer{timer_map = TimerMap})
|
||||||
when is_integer(Id), is_binary(Payload), is_integer(RetryTimes), RetryTimes >= 0 ->
|
when is_integer(Id), is_binary(Payload), is_integer(RetryTimes), RetryTimes >= 0 ->
|
||||||
|
|||||||
@ -30,10 +30,10 @@
|
|||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|
||||||
%% @doc Spawns the server and registers the local name (unique)
|
%% @doc Spawns the server and registers the local name (unique)
|
||||||
-spec(start_link(LocalName :: atom(), Endpoint :: #endpoint{}) ->
|
-spec(start_link(LocalName :: term(), Endpoint :: #endpoint{}) ->
|
||||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||||
start_link(LocalName, Endpoint = #endpoint{config = #http_endpoint{}}) when is_atom(LocalName) ->
|
start_link(LocalName, Endpoint = #endpoint{config = #http_endpoint{}}) ->
|
||||||
gen_server:start_link({local, LocalName}, ?MODULE, [Endpoint], []).
|
gen_server:start_link({via, gproc, {n, l, LocalName}}, ?MODULE, [Endpoint], []).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% gen_server callbacks
|
%%% gen_server callbacks
|
||||||
|
|||||||
@ -34,10 +34,10 @@
|
|||||||
%%% API
|
%%% API
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|
||||||
-spec start_link(LocalName :: atom(), Endpoint :: #endpoint{}) ->
|
-spec start_link(LocalName :: term(), Endpoint :: #endpoint{}) ->
|
||||||
{ok, pid()} | ignore | {error, term()}.
|
{ok, pid()} | ignore | {error, term()}.
|
||||||
start_link(LocalName, Endpoint = #endpoint{}) when is_atom(LocalName) ->
|
start_link(LocalName, Endpoint = #endpoint{}) ->
|
||||||
gen_statem:start_link({local, LocalName}, ?MODULE, [Endpoint], []).
|
gen_statem:start_link({via, gproc, {n, l, LocalName}}, ?MODULE, [Endpoint], []).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% gen_statem callbacks
|
%%% gen_statem callbacks
|
||||||
|
|||||||
@ -35,10 +35,10 @@
|
|||||||
%%% API
|
%%% API
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|
||||||
-spec start_link(LocalName :: atom(), Endpoint :: #endpoint{}) ->
|
-spec start_link(LocalName :: term(), Endpoint :: #endpoint{}) ->
|
||||||
{ok, pid()} | ignore | {error, term()}.
|
{ok, pid()} | ignore | {error, term()}.
|
||||||
start_link(LocalName, Endpoint = #endpoint{}) when is_atom(LocalName) ->
|
start_link(LocalName, Endpoint = #endpoint{}) ->
|
||||||
gen_statem:start_link({local, LocalName}, ?MODULE, [Endpoint], []).
|
gen_statem:start_link({via, gproc, {n, l, LocalName}}, ?MODULE, [Endpoint], []).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% gen_statem callbacks
|
%%% gen_statem callbacks
|
||||||
|
|||||||
@ -59,8 +59,24 @@ ensured_endpoint_started(Endpoint = #endpoint{}) ->
|
|||||||
-spec delete_endpoint(Id :: integer()) -> ok | {error, Reason :: any()}.
|
-spec delete_endpoint(Id :: integer()) -> ok | {error, Reason :: any()}.
|
||||||
delete_endpoint(Id) when is_integer(Id) ->
|
delete_endpoint(Id) when is_integer(Id) ->
|
||||||
Name = endpoint:get_name(Id),
|
Name = endpoint:get_name(Id),
|
||||||
supervisor:terminate_child(?MODULE, Name),
|
case supervisor:terminate_child(?MODULE, Name) of
|
||||||
supervisor:delete_child(?MODULE, Name).
|
ok ->
|
||||||
|
delete_endpoint_child(Name);
|
||||||
|
{error, not_found} ->
|
||||||
|
delete_endpoint_child(Name);
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
delete_endpoint_child(Name) ->
|
||||||
|
case supervisor:delete_child(?MODULE, Name) of
|
||||||
|
ok ->
|
||||||
|
ok;
|
||||||
|
{error, not_found} ->
|
||||||
|
ok;
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
child_spec(Endpoint = #endpoint{id = Id}) ->
|
child_spec(Endpoint = #endpoint{id = Id}) ->
|
||||||
Name = endpoint:get_name(Id),
|
Name = endpoint:get_name(Id),
|
||||||
|
|||||||
@ -50,20 +50,19 @@
|
|||||||
-spec get_pid(UUID :: binary()) -> undefined | pid().
|
-spec get_pid(UUID :: binary()) -> undefined | pid().
|
||||||
get_pid(UUID) when is_binary(UUID) ->
|
get_pid(UUID) when is_binary(UUID) ->
|
||||||
Name = get_name(UUID),
|
Name = get_name(UUID),
|
||||||
whereis(Name).
|
gproc:whereis_name({n, l, Name}).
|
||||||
|
|
||||||
-spec get_name(UUID :: binary()) -> atom().
|
-spec get_name(UUID :: binary()) -> term().
|
||||||
get_name(UUID) when is_binary(UUID) ->
|
get_name(UUID) when is_binary(UUID) ->
|
||||||
binary_to_atom(<<"iot_host:", UUID/binary>>).
|
{iot_host, UUID}.
|
||||||
|
|
||||||
-spec get_alias_name(HostId :: integer()) -> atom().
|
-spec get_alias_name(HostId :: integer()) -> term().
|
||||||
get_alias_name(HostId0) when is_integer(HostId0) ->
|
get_alias_name(HostId0) when is_integer(HostId0) ->
|
||||||
HostId = integer_to_binary(HostId0),
|
{iot_host_id, HostId0}.
|
||||||
binary_to_atom(<<"iot_host_id:", HostId/binary>>).
|
|
||||||
|
|
||||||
-spec kill(UUID :: binary()) -> no_return().
|
-spec kill(UUID :: binary()) -> no_return().
|
||||||
kill(UUID) when is_binary(UUID) ->
|
kill(UUID) when is_binary(UUID) ->
|
||||||
case whereis(get_name(UUID)) of
|
case get_pid(UUID) of
|
||||||
undefined ->
|
undefined ->
|
||||||
ok;
|
ok;
|
||||||
Pid ->
|
Pid ->
|
||||||
@ -152,8 +151,8 @@ heartbeat(Pid) when is_pid(Pid) ->
|
|||||||
%% @doc Creates a gen_statem process which calls Module:init/1 to
|
%% @doc Creates a gen_statem process which calls Module:init/1 to
|
||||||
%% initialize. To ensure a synchronized start-up procedure, this
|
%% initialize. To ensure a synchronized start-up procedure, this
|
||||||
%% function does not return until Module:init/1 has returned.
|
%% function does not return until Module:init/1 has returned.
|
||||||
start_link(Name, UUID) when is_atom(Name), is_binary(UUID) ->
|
start_link(Name, UUID) when is_binary(UUID) ->
|
||||||
gen_statem:start_link({local, Name}, ?MODULE, [UUID], []).
|
gen_statem:start_link({via, gproc, {n, l, Name}}, ?MODULE, [UUID], []).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% gen_statem callbacks
|
%%% gen_statem callbacks
|
||||||
@ -306,15 +305,7 @@ handle_event(cast, heartbeat, _, State = #state{heartbeat_counter = HeartbeatCou
|
|||||||
%% 没有收到心跳包,主机下线, 设备状态不变
|
%% 没有收到心跳包,主机下线, 设备状态不变
|
||||||
handle_event(info, {timeout, _, heartbeat_ticker}, _, State = #state{uuid = UUID, heartbeat_counter = 0, channel_pid = ChannelPid}) ->
|
handle_event(info, {timeout, _, heartbeat_ticker}, _, State = #state{uuid = UUID, heartbeat_counter = 0, channel_pid = ChannelPid}) ->
|
||||||
logger:warning("[iot_host] uuid: ~p, heartbeat lost, devices will unknown", [UUID]),
|
logger:warning("[iot_host] uuid: ~p, heartbeat lost, devices will unknown", [UUID]),
|
||||||
{ok, #host_info{status = Status}} = iot_api_client:get_host_by_uuid(UUID),
|
maybe_mark_host_offline(UUID),
|
||||||
case Status of
|
|
||||||
?HOST_NOT_JOINED ->
|
|
||||||
logger:debug("[iot_host] host: ~p, host_maybe_offline, host not joined, can not change to offline", [UUID]);
|
|
||||||
?HOST_OFFLINE ->
|
|
||||||
logger:debug("[iot_host] host: ~p, host_maybe_offline, host now is offline, do nothing", [UUID]);
|
|
||||||
?HOST_ONLINE ->
|
|
||||||
iot_api_client:change_host_status(UUID, ?HOST_OFFLINE)
|
|
||||||
end,
|
|
||||||
|
|
||||||
%% 关闭channel,主机需要重新连接,才能保存状态的一致
|
%% 关闭channel,主机需要重新连接,才能保存状态的一致
|
||||||
is_pid(ChannelPid) andalso ssl_channel:stop(ChannelPid, closed),
|
is_pid(ChannelPid) andalso ssl_channel:stop(ChannelPid, closed),
|
||||||
@ -387,3 +378,20 @@ flush_reply(Ref) ->
|
|||||||
after 0 ->
|
after 0 ->
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec maybe_mark_host_offline(binary()) -> ok.
|
||||||
|
maybe_mark_host_offline(UUID) ->
|
||||||
|
case iot_api_client:get_host_by_uuid(UUID) of
|
||||||
|
{ok, #host_info{status = ?HOST_NOT_JOINED}} ->
|
||||||
|
logger:debug("[iot_host] host: ~p, host_maybe_offline, host not joined, can not change to offline", [UUID]),
|
||||||
|
ok;
|
||||||
|
{ok, #host_info{status = ?HOST_OFFLINE}} ->
|
||||||
|
logger:debug("[iot_host] host: ~p, host_maybe_offline, host now is offline, do nothing", [UUID]),
|
||||||
|
ok;
|
||||||
|
{ok, #host_info{status = ?HOST_ONLINE}} ->
|
||||||
|
_ = iot_api_client:change_host_status(UUID, ?HOST_OFFLINE),
|
||||||
|
ok;
|
||||||
|
Other ->
|
||||||
|
logger:warning("[iot_host] host: ~p, load status failed while marking offline: ~p", [UUID, Other]),
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|||||||
@ -24,14 +24,18 @@ ensured_host_started(UUID) when is_binary(UUID) ->
|
|||||||
case iot_host:get_pid(UUID) of
|
case iot_host:get_pid(UUID) of
|
||||||
undefined ->
|
undefined ->
|
||||||
%% 尝试删除下host对应的信息
|
%% 尝试删除下host对应的信息
|
||||||
delete_host(UUID),
|
case delete_host(UUID) of
|
||||||
case supervisor:start_child(?MODULE, child_spec(UUID)) of
|
ok ->
|
||||||
{ok, Pid} when is_pid(Pid) ->
|
case supervisor:start_child(?MODULE, child_spec(UUID)) of
|
||||||
{ok, Pid};
|
{ok, Pid} when is_pid(Pid) ->
|
||||||
{error, {'already_started', Pid}} when is_pid(Pid) ->
|
{ok, Pid};
|
||||||
{ok, Pid};
|
{error, {'already_started', Pid}} when is_pid(Pid) ->
|
||||||
{error, Error} ->
|
{ok, Pid};
|
||||||
{error, Error}
|
{error, Error} ->
|
||||||
|
{error, Error}
|
||||||
|
end;
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
end;
|
end;
|
||||||
Pid when is_pid(Pid) ->
|
Pid when is_pid(Pid) ->
|
||||||
{ok, Pid}
|
{ok, Pid}
|
||||||
@ -39,14 +43,35 @@ ensured_host_started(UUID) when is_binary(UUID) ->
|
|||||||
|
|
||||||
delete_host(UUID) when is_binary(UUID) ->
|
delete_host(UUID) when is_binary(UUID) ->
|
||||||
Id = iot_host:get_name(UUID),
|
Id = iot_host:get_name(UUID),
|
||||||
ok = supervisor:terminate_child(?MODULE, Id),
|
case supervisor:terminate_child(?MODULE, Id) of
|
||||||
|
ok ->
|
||||||
|
delete_host_child(Id, UUID);
|
||||||
|
{error, not_found} ->
|
||||||
|
delete_host_child(Id, UUID);
|
||||||
|
{error, Reason} ->
|
||||||
|
logger:warning("[iot_host_sup] terminate host: ~p failed: ~p", [UUID, Reason]),
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
delete_host_child(Id, UUID) ->
|
||||||
case supervisor:delete_child(?MODULE, Id) of
|
case supervisor:delete_child(?MODULE, Id) of
|
||||||
|
ok ->
|
||||||
|
ok;
|
||||||
|
{error, not_found} ->
|
||||||
|
ok;
|
||||||
{error, running} ->
|
{error, running} ->
|
||||||
%% ensure killed then delete again
|
%% ensure killed then delete again
|
||||||
iot_host:kill(UUID),
|
iot_host:kill(UUID),
|
||||||
supervisor:delete_child(?MODULE, Id);
|
case supervisor:delete_child(?MODULE, Id) of
|
||||||
_ ->
|
ok ->
|
||||||
ok
|
ok;
|
||||||
|
{error, not_found} ->
|
||||||
|
ok;
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end;
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec child_spec(UUID :: binary()) -> map().
|
-spec child_spec(UUID :: binary()) -> map().
|
||||||
|
|||||||
@ -13,18 +13,32 @@ start(_StartType, _StartArgs) ->
|
|||||||
io:setopts([{encoding, unicode}]),
|
io:setopts([{encoding, unicode}]),
|
||||||
%% 加速内存的回收
|
%% 加速内存的回收
|
||||||
erlang:system_flag(fullsweep_after, 16),
|
erlang:system_flag(fullsweep_after, 16),
|
||||||
%% 启动mnesia数据库
|
try
|
||||||
start_mnesia(),
|
%% 启动mnesia数据库
|
||||||
|
start_mnesia(),
|
||||||
|
|
||||||
%% 启动http服务
|
%% 启动http服务。当前 supervisor 初始化依赖本机 simulator API,因此保留此顺序。
|
||||||
start_http_server(),
|
start_http_server(),
|
||||||
|
|
||||||
%% 启动ssl服务
|
%% 启动ssl服务
|
||||||
start_ssl_server(),
|
start_ssl_server(),
|
||||||
|
|
||||||
iot_sup:start_link().
|
case iot_sup:start_link() of
|
||||||
|
{ok, _Pid} = Ok ->
|
||||||
|
Ok;
|
||||||
|
Error ->
|
||||||
|
stop_started_services(),
|
||||||
|
Error
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
Class:Reason:Stack ->
|
||||||
|
stop_started_services(),
|
||||||
|
logger:warning("[iot_app] start failed, class: ~p, reason: ~p, stack: ~p", [Class, Reason, Stack]),
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
stop(_State) ->
|
stop(_State) ->
|
||||||
|
stop_started_services(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%% internal functions
|
%% internal functions
|
||||||
@ -96,6 +110,12 @@ start_ssl_server() ->
|
|||||||
{ok, _} = ranch:start_listener(ssl_server, ranch_ssl, TransOpts, ssl_channel, []),
|
{ok, _} = ranch:start_listener(ssl_server, ranch_ssl, TransOpts, ssl_channel, []),
|
||||||
logger:debug("[iot_app] the ssl server start at: ~p", [Port]).
|
logger:debug("[iot_app] the ssl server start at: ~p", [Port]).
|
||||||
|
|
||||||
|
stop_started_services() ->
|
||||||
|
_ = cowboy:stop_listener(http_listener),
|
||||||
|
_ = ranch:stop_listener(ssl_server),
|
||||||
|
_ = mnesia:stop(),
|
||||||
|
ok.
|
||||||
|
|
||||||
-spec ensure_mnesia_schema() -> any().
|
-spec ensure_mnesia_schema() -> any().
|
||||||
ensure_mnesia_schema() ->
|
ensure_mnesia_schema() ->
|
||||||
case mnesia:system_info(use_dir) of
|
case mnesia:system_info(use_dir) of
|
||||||
|
|||||||
@ -117,18 +117,9 @@ hex(N) ->
|
|||||||
|
|
||||||
%% 转换映射器
|
%% 转换映射器
|
||||||
-spec parse_mapper(Mapper :: binary() | string()) -> error | {ok, F :: fun((binary(), any()) -> any())}.
|
-spec parse_mapper(Mapper :: binary() | string()) -> error | {ok, F :: fun((binary(), any()) -> any())}.
|
||||||
parse_mapper(Mapper) when is_binary(Mapper) ->
|
parse_mapper(_Mapper) ->
|
||||||
parse_mapper(binary_to_list(Mapper));
|
%% Dynamic Erlang eval is intentionally disabled in runtime code.
|
||||||
parse_mapper(Mapper) when is_list(Mapper) ->
|
error.
|
||||||
{ok, Tokens, _} = erl_scan:string(Mapper),
|
|
||||||
{ok, ExprList} = erl_parse:parse_exprs(Tokens),
|
|
||||||
{value, F, _} = erl_eval:exprs(ExprList, []),
|
|
||||||
case is_function(F, 2) orelse is_function(F, 3) of
|
|
||||||
true ->
|
|
||||||
{ok, F};
|
|
||||||
false ->
|
|
||||||
error
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec float_to_binary(Num :: number(), integer()) -> binary().
|
-spec float_to_binary(Num :: number(), integer()) -> binary().
|
||||||
float_to_binary(V, _) when is_integer(V) ->
|
float_to_binary(V, _) when is_integer(V) ->
|
||||||
@ -142,4 +133,4 @@ assert(true, _) ->
|
|||||||
assert(false, F) when is_function(F) ->
|
assert(false, F) when is_function(F) ->
|
||||||
F();
|
F();
|
||||||
assert(false, Msg) ->
|
assert(false, Msg) ->
|
||||||
throw(Msg).
|
throw(Msg).
|
||||||
|
|||||||
@ -171,7 +171,8 @@ handle_request("POST", "/endpoint/test", _, #{<<"protocol">> := <<"kafka">>, <<"
|
|||||||
undefined ->
|
undefined ->
|
||||||
BaseConfig
|
BaseConfig
|
||||||
end,
|
end,
|
||||||
ClientId = list_to_atom("brod_client_test:" ++ iot_util:rand_bytes(16)),
|
ClientId = brod_client_test,
|
||||||
|
_ = catch brod:stop_client(ClientId),
|
||||||
|
|
||||||
case catch brod:start_link_client(BootstrapServers, ClientId, ClientConfig) of
|
case catch brod:start_link_client(BootstrapServers, ClientId, ClientConfig) of
|
||||||
{ok, _ClientPid} ->
|
{ok, _ClientPid} ->
|
||||||
@ -181,6 +182,7 @@ handle_request("POST", "/endpoint/test", _, #{<<"protocol">> := <<"kafka">>, <<"
|
|||||||
{ok, 200, iot_util:json_data(<<"ok">>)};
|
{ok, 200, iot_util:json_data(<<"ok">>)};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
logger:debug("[endpint_handler] start_producer: ~p, get error: ~p", [ClientId, Reason]),
|
logger:debug("[endpint_handler] start_producer: ~p, get error: ~p", [ClientId, Reason]),
|
||||||
|
_ = catch brod:stop_client(ClientId),
|
||||||
{ok, 200, iot_util:json_error(-1, <<"config kafka server failed">>)}
|
{ok, 200, iot_util:json_error(-1, <<"config kafka server failed">>)}
|
||||||
end;
|
end;
|
||||||
Error ->
|
Error ->
|
||||||
|
|||||||
@ -12,14 +12,25 @@
|
|||||||
%% API
|
%% API
|
||||||
-export([init/2]).
|
-export([init/2]).
|
||||||
|
|
||||||
|
-define(MAX_BODY_BYTES, 10 * 1024 * 1024).
|
||||||
|
|
||||||
init(Req0, Opts = [Mod|_]) ->
|
init(Req0, Opts = [Mod|_]) ->
|
||||||
ok = iot_log:set_metadata(),
|
ok = iot_log:set_metadata(),
|
||||||
Method = binary_to_list(cowboy_req:method(Req0)),
|
Method = binary_to_list(cowboy_req:method(Req0)),
|
||||||
Path = binary_to_list(cowboy_req:path(Req0)),
|
Path = binary_to_list(cowboy_req:path(Req0)),
|
||||||
GetParams0 = cowboy_req:parse_qs(Req0),
|
GetParams0 = cowboy_req:parse_qs(Req0),
|
||||||
GetParams = maps:from_list(GetParams0),
|
GetParams = maps:from_list(GetParams0),
|
||||||
{ok, PostParams, Req1} = parse_body(Req0),
|
case parse_body(Req0) of
|
||||||
|
{ok, PostParams, Req1} ->
|
||||||
|
handle_request(Mod, Method, Path, GetParams, PostParams, Req1, Opts);
|
||||||
|
{error, StatusCode, Resp, Req1} ->
|
||||||
|
Req2 = cowboy_req:reply(StatusCode, #{
|
||||||
|
<<"Content-Type">> => <<"application/json;charset=utf-8">>
|
||||||
|
}, Resp, Req1),
|
||||||
|
{ok, Req2, Opts}
|
||||||
|
end.
|
||||||
|
|
||||||
|
handle_request(Mod, Method, Path, GetParams, PostParams, Req1, Opts) ->
|
||||||
try Mod:handle_request(Method, Path, GetParams, PostParams) of
|
try Mod:handle_request(Method, Path, GetParams, PostParams) of
|
||||||
{ok, StatusCode, Resp} ->
|
{ok, StatusCode, Resp} ->
|
||||||
logger:debug("[http_protocol] request path: ~p, get_params: ~p, post_params: ~p, response: ~ts",
|
logger:debug("[http_protocol] request path: ~p, get_params: ~p, post_params: ~p, response: ~ts",
|
||||||
@ -61,17 +72,20 @@ parse_body(Req0) ->
|
|||||||
ContentType = cowboy_req:header(<<"content-type">>, Req0),
|
ContentType = cowboy_req:header(<<"content-type">>, Req0),
|
||||||
case ContentType of
|
case ContentType of
|
||||||
<<"application/json", _/binary>> ->
|
<<"application/json", _/binary>> ->
|
||||||
{ok, Body, Req1} = read_body(Req0),
|
case read_body(Req0) of
|
||||||
case Body =/= <<"">> of
|
{ok, Body, Req1} ->
|
||||||
true ->
|
decode_json_body(Body, Req1);
|
||||||
{ok, catch json:decode(Body), Req1};
|
{error, payload_too_large, Req1} ->
|
||||||
false ->
|
{error, 413, iot_util:json_error(413, <<"payload too large">>), Req1}
|
||||||
{ok, #{}, Req1}
|
|
||||||
end;
|
end;
|
||||||
<<"application/x-www-form-urlencoded">> ->
|
<<"application/x-www-form-urlencoded">> ->
|
||||||
{ok, PostParams0, Req1} = cowboy_req:read_urlencoded_body(Req0),
|
case cowboy_req:read_urlencoded_body(Req0) of
|
||||||
PostParams = maps:from_list(PostParams0),
|
{ok, PostParams0, Req1} ->
|
||||||
{ok, PostParams, Req1};
|
PostParams = maps:from_list(PostParams0),
|
||||||
|
{ok, PostParams, Req1};
|
||||||
|
{more, _PostParams0, Req1} ->
|
||||||
|
{error, 413, iot_util:json_error(413, <<"payload too large">>), Req1}
|
||||||
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
{ok, #{}, Req0}
|
{ok, #{}, Req0}
|
||||||
end.
|
end.
|
||||||
@ -82,7 +96,33 @@ read_body(Req) ->
|
|||||||
read_body(Req, AccData) ->
|
read_body(Req, AccData) ->
|
||||||
case cowboy_req:read_body(Req) of
|
case cowboy_req:read_body(Req) of
|
||||||
{ok, Data, Req1} ->
|
{ok, Data, Req1} ->
|
||||||
{ok, <<AccData/binary, Data/binary>>, Req1};
|
NAccData = <<AccData/binary, Data/binary>>,
|
||||||
|
case byte_size(NAccData) =< ?MAX_BODY_BYTES of
|
||||||
|
true ->
|
||||||
|
{ok, NAccData, Req1};
|
||||||
|
false ->
|
||||||
|
{error, payload_too_large, Req1}
|
||||||
|
end;
|
||||||
{more, Data, Req1} ->
|
{more, Data, Req1} ->
|
||||||
read_body(Req1, <<AccData/binary, Data/binary>>)
|
NAccData = <<AccData/binary, Data/binary>>,
|
||||||
|
case byte_size(NAccData) =< ?MAX_BODY_BYTES of
|
||||||
|
true ->
|
||||||
|
read_body(Req1, NAccData);
|
||||||
|
false ->
|
||||||
|
{error, payload_too_large, Req1}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
decode_json_body(<<"">>, Req) ->
|
||||||
|
{ok, #{}, Req};
|
||||||
|
decode_json_body(Body, Req) ->
|
||||||
|
case catch json:decode(Body) of
|
||||||
|
{'EXIT', _} ->
|
||||||
|
{error, 400, iot_util:json_error(400, <<"invalid json">>), Req};
|
||||||
|
{error, _} ->
|
||||||
|
{error, 400, iot_util:json_error(400, <<"invalid json">>), Req};
|
||||||
|
Decoded when is_map(Decoded); is_list(Decoded) ->
|
||||||
|
{ok, Decoded, Req};
|
||||||
|
_ ->
|
||||||
|
{error, 400, iot_util:json_error(400, <<"invalid json">>), Req}
|
||||||
end.
|
end.
|
||||||
|
|||||||
@ -142,13 +142,19 @@ handle_info({'DOWN', _, process, HostPid, Reason}, State = #state{uuid = UUID, h
|
|||||||
|
|
||||||
handle_info({ssl, Socket, PacketBin}, State = #state{transport = Transport, socket = Socket, host_pid = HostPid, inflight = Inflight})
|
handle_info({ssl, Socket, PacketBin}, State = #state{transport = Transport, socket = Socket, host_pid = HostPid, inflight = Inflight})
|
||||||
when is_binary(PacketBin) ->
|
when is_binary(PacketBin) ->
|
||||||
case binary_to_term(PacketBin, [safe]) of
|
case catch binary_to_term(PacketBin, [safe]) of
|
||||||
{request, PacketId, Body} ->
|
{request, PacketId, Body} ->
|
||||||
handle_request_frame(PacketId, Body, Transport, Socket, State);
|
handle_request_frame(PacketId, Body, Transport, Socket, State);
|
||||||
{message, Body} ->
|
{message, Body} ->
|
||||||
handle_message_frame(Body, HostPid, State);
|
handle_message_frame(Body, HostPid, State);
|
||||||
{response, PacketId, Response} ->
|
{response, PacketId, Response} ->
|
||||||
handle_response_frame(PacketId, Response, Inflight, State)
|
handle_response_frame(PacketId, Response, Inflight, State);
|
||||||
|
{'EXIT', Reason} ->
|
||||||
|
logger:warning("[ssl_channel] invalid packet: ~p", [Reason]),
|
||||||
|
{stop, bad_packet, State};
|
||||||
|
Other ->
|
||||||
|
logger:warning("[ssl_channel] unsupported packet: ~p", [Other]),
|
||||||
|
{stop, bad_packet, State}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info({ssl_closed, Socket}, State = #state{socket = Socket}) ->
|
handle_info({ssl_closed, Socket}, State = #state{socket = Socket}) ->
|
||||||
@ -300,7 +306,8 @@ decode_reply(_Reply) ->
|
|||||||
ok | {error, Reason :: binary()}.
|
ok | {error, Reason :: binary()}.
|
||||||
auth(Token, UUID, Timestamp) when is_binary(Token), is_binary(UUID), is_integer(Timestamp) ->
|
auth(Token, UUID, Timestamp) when is_binary(Token), is_binary(UUID), is_integer(Timestamp) ->
|
||||||
%% 1分钟内有效
|
%% 1分钟内有效
|
||||||
case iot_util:current_time() - Timestamp =< 60 of
|
Now = iot_util:current_time(),
|
||||||
|
case Timestamp =< Now andalso Now - Timestamp =< 60 of
|
||||||
true ->
|
true ->
|
||||||
case efka_client_store:auth(UUID, Token) of
|
case efka_client_store:auth(UUID, Token) of
|
||||||
true ->
|
true ->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user