Add message handler callbacks option to emqx_client
In this commit, msg_handler option is added to emqx_client. so the caller can provide callbacks to handle puback, publish, as well as disconnected events instead of always delivered as message like Owner ! {publish, Msg} to the owner process. This is to make it ready to implement emqx_portal_connect on top of emqx_client.
This commit is contained in:
parent
fbe67e6784
commit
141af0d02c
|
@ -59,7 +59,7 @@
|
||||||
|
|
||||||
-define(RESPONSE_TIMEOUT_SECONDS, timer:seconds(5)).
|
-define(RESPONSE_TIMEOUT_SECONDS, timer:seconds(5)).
|
||||||
|
|
||||||
-define(NO_HANDLER, undefined).
|
-define(NO_REQ_HANDLER, undefined).
|
||||||
|
|
||||||
-define(NO_GROUP, <<>>).
|
-define(NO_GROUP, <<>>).
|
||||||
|
|
||||||
|
@ -67,10 +67,23 @@
|
||||||
|
|
||||||
-type(host() :: inet:ip_address() | inet:hostname()).
|
-type(host() :: inet:ip_address() | inet:hostname()).
|
||||||
|
|
||||||
-type corr_data() :: binary().
|
-type(corr_data() :: binary()).
|
||||||
|
|
||||||
|
%% NOTE: Message handler is different from request handler.
|
||||||
|
%% Message handler is a set of callbacks defined to handle MQTT messages as well as
|
||||||
|
%% the disconnect event.
|
||||||
|
%% Request handler is a callback to handle received MQTT message as in 'request',
|
||||||
|
%% and publish another MQTT message back to the defined topic as in 'response'.
|
||||||
|
%% `owner' and `msg_handler' has no effect when `request_handler' is set.
|
||||||
|
-define(NO_MSG_HDLR, undefined).
|
||||||
|
-type(msg_handler() :: #{puback := fun((_) -> any()),
|
||||||
|
publish := fun((emqx_types:message()) -> any()),
|
||||||
|
disconnected := fun(({reason_code(), _Properties :: term()}) -> any())
|
||||||
|
}).
|
||||||
|
|
||||||
-type(option() :: {name, atom()}
|
-type(option() :: {name, atom()}
|
||||||
| {owner, pid()}
|
| {owner, pid()}
|
||||||
|
| {msg_handler, msg_handler()}
|
||||||
| {host, host()}
|
| {host, host()}
|
||||||
| {hosts, [{host(), inet:port_number()}]}
|
| {hosts, [{host(), inet:port_number()}]}
|
||||||
| {port, inet:port_number()}
|
| {port, inet:port_number()}
|
||||||
|
@ -102,6 +115,7 @@
|
||||||
|
|
||||||
-record(state, {name :: atom(),
|
-record(state, {name :: atom(),
|
||||||
owner :: pid(),
|
owner :: pid(),
|
||||||
|
msg_handler :: ?NO_MSG_HDLR | msg_handler(),
|
||||||
host :: host(),
|
host :: host(),
|
||||||
port :: inet:port_number(),
|
port :: inet:port_number(),
|
||||||
hosts :: [{host(), inet:port_number()}],
|
hosts :: [{host(), inet:port_number()}],
|
||||||
|
@ -497,7 +511,7 @@ init([Options]) ->
|
||||||
auto_ack = true,
|
auto_ack = true,
|
||||||
ack_timeout = ?DEFAULT_ACK_TIMEOUT,
|
ack_timeout = ?DEFAULT_ACK_TIMEOUT,
|
||||||
retry_interval = 0,
|
retry_interval = 0,
|
||||||
request_handler = ?NO_HANDLER,
|
request_handler = ?NO_REQ_HANDLER,
|
||||||
connect_timeout = ?DEFAULT_CONNECT_TIMEOUT,
|
connect_timeout = ?DEFAULT_CONNECT_TIMEOUT,
|
||||||
last_packet_id = 1}),
|
last_packet_id = 1}),
|
||||||
{ok, initialized, init_parse_state(State)}.
|
{ok, initialized, init_parse_state(State)}.
|
||||||
|
@ -516,6 +530,8 @@ init([{name, Name} | Opts], State) ->
|
||||||
init([{owner, Owner} | Opts], State) when is_pid(Owner) ->
|
init([{owner, Owner} | Opts], State) when is_pid(Owner) ->
|
||||||
link(Owner),
|
link(Owner),
|
||||||
init(Opts, State#state{owner = Owner});
|
init(Opts, State#state{owner = Owner});
|
||||||
|
init([{msg_handler, Hdlr} | Opts], State) ->
|
||||||
|
init(Opts, State#state{msg_handler = Hdlr});
|
||||||
init([{host, Host} | Opts], State) ->
|
init([{host, Host} | Opts], State) ->
|
||||||
init(Opts, State#state{host = Host});
|
init(Opts, State#state{host = Host});
|
||||||
init([{port, Port} | Opts], State) ->
|
init([{port, Port} | Opts], State) ->
|
||||||
|
@ -857,12 +873,12 @@ connected(cast, Packet = ?PUBLISH_PACKET(?QOS_2, _PacketId), State) ->
|
||||||
publish_process(?QOS_2, Packet, State);
|
publish_process(?QOS_2, Packet, State);
|
||||||
|
|
||||||
connected(cast, ?PUBACK_PACKET(PacketId, ReasonCode, Properties),
|
connected(cast, ?PUBACK_PACKET(PacketId, ReasonCode, Properties),
|
||||||
State = #state{owner = Owner, inflight = Inflight}) ->
|
State = #state{inflight = Inflight}) ->
|
||||||
case emqx_inflight:lookup(PacketId, Inflight) of
|
case emqx_inflight:lookup(PacketId, Inflight) of
|
||||||
{value, {publish, #mqtt_msg{packet_id = PacketId}, _Ts}} ->
|
{value, {publish, #mqtt_msg{packet_id = PacketId}, _Ts}} ->
|
||||||
Owner ! {puback, #{packet_id => PacketId,
|
ok = eval_msg_handler(State, puback, #{packet_id => PacketId,
|
||||||
reason_code => ReasonCode,
|
reason_code => ReasonCode,
|
||||||
properties => Properties}},
|
properties => Properties}),
|
||||||
{keep_state, State#state{inflight = emqx_inflight:delete(PacketId, Inflight)}};
|
{keep_state, State#state{inflight = emqx_inflight:delete(PacketId, Inflight)}};
|
||||||
none ->
|
none ->
|
||||||
emqx_logger:warning("Unexpected PUBACK: ~p", [PacketId]),
|
emqx_logger:warning("Unexpected PUBACK: ~p", [PacketId]),
|
||||||
|
@ -899,12 +915,12 @@ connected(cast, ?PUBREL_PACKET(PacketId),
|
||||||
end;
|
end;
|
||||||
|
|
||||||
connected(cast, ?PUBCOMP_PACKET(PacketId, ReasonCode, Properties),
|
connected(cast, ?PUBCOMP_PACKET(PacketId, ReasonCode, Properties),
|
||||||
State = #state{owner = Owner, inflight = Inflight}) ->
|
State = #state{inflight = Inflight}) ->
|
||||||
case emqx_inflight:lookup(PacketId, Inflight) of
|
case emqx_inflight:lookup(PacketId, Inflight) of
|
||||||
{value, {pubrel, _PacketId, _Ts}} ->
|
{value, {pubrel, _PacketId, _Ts}} ->
|
||||||
Owner ! {puback, #{packet_id => PacketId,
|
ok = eval_msg_handler(State, puback, #{packet_id => PacketId,
|
||||||
reason_code => ReasonCode,
|
reason_code => ReasonCode,
|
||||||
properties => Properties}},
|
properties => Properties}),
|
||||||
{keep_state, State#state{inflight = emqx_inflight:delete(PacketId, Inflight)}};
|
{keep_state, State#state{inflight = emqx_inflight:delete(PacketId, Inflight)}};
|
||||||
none ->
|
none ->
|
||||||
emqx_logger:warning("Unexpected PUBCOMP Packet: ~p", [PacketId]),
|
emqx_logger:warning("Unexpected PUBCOMP Packet: ~p", [PacketId]),
|
||||||
|
@ -943,9 +959,8 @@ connected(cast, ?PACKET(?PINGRESP), State) ->
|
||||||
false -> {keep_state, State}
|
false -> {keep_state, State}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
connected(cast, ?DISCONNECT_PACKET(ReasonCode, Properties),
|
connected(cast, ?DISCONNECT_PACKET(ReasonCode, Properties), State) ->
|
||||||
State = #state{owner = Owner}) ->
|
ok = eval_msg_handler(State, disconnected, {ReasonCode, Properties}),
|
||||||
Owner ! {disconnected, ReasonCode, Properties},
|
|
||||||
{stop, disconnected, State};
|
{stop, disconnected, State};
|
||||||
|
|
||||||
connected(info, {timeout, _TRef, keepalive}, State = #state{force_ping = true}) ->
|
connected(info, {timeout, _TRef, keepalive}, State = #state{force_ping = true}) ->
|
||||||
|
@ -1101,8 +1116,8 @@ assign_id(?NO_CLIENT_ID, Props) ->
|
||||||
assign_id(Id, _Props) ->
|
assign_id(Id, _Props) ->
|
||||||
Id.
|
Id.
|
||||||
|
|
||||||
publish_process(?QOS_1, Packet = ?PUBLISH_PACKET(?QOS_1, PacketId), State = #state{auto_ack = AutoAck}) ->
|
publish_process(?QOS_1, Packet = ?PUBLISH_PACKET(?QOS_1, PacketId), State0 = #state{auto_ack = AutoAck}) ->
|
||||||
_ = deliver(packet_to_msg(Packet), State),
|
State = deliver(packet_to_msg(Packet), State0),
|
||||||
case AutoAck of
|
case AutoAck of
|
||||||
true -> send_puback(?PUBACK_PACKET(PacketId), State);
|
true -> send_puback(?PUBACK_PACKET(PacketId), State);
|
||||||
false -> {keep_state, State}
|
false -> {keep_state, State}
|
||||||
|
@ -1116,18 +1131,11 @@ publish_process(?QOS_2, Packet = ?PUBLISH_PACKET(?QOS_2, PacketId),
|
||||||
Stop -> Stop
|
Stop -> Stop
|
||||||
end.
|
end.
|
||||||
|
|
||||||
response_publish(undefined, State, _QoS, _Payload) ->
|
response_publish(#{'Response-Topic' := ResponseTopic} = Properties,
|
||||||
State;
|
State = #state{request_handler = RequestHandler}, QoS, Payload)
|
||||||
response_publish(Properties, State = #state{request_handler = RequestHandler}, QoS, Payload) ->
|
when RequestHandler =/= ?NO_REQ_HANDLER ->
|
||||||
case maps:find('Response-Topic', Properties) of
|
do_publish(ResponseTopic, Properties, State, QoS, Payload);
|
||||||
{ok, ResponseTopic} ->
|
response_publish(_Properties, State, _QoS, _Payload) -> State.
|
||||||
case RequestHandler of
|
|
||||||
?NO_HANDLER -> State;
|
|
||||||
_ -> do_publish(ResponseTopic, Properties, State, QoS, Payload)
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
State
|
|
||||||
end.
|
|
||||||
|
|
||||||
do_publish(ResponseTopic, Properties, State = #state{request_handler = RequestHandler}, ?QOS_0, Payload) ->
|
do_publish(ResponseTopic, Properties, State = #state{request_handler = RequestHandler}, ?QOS_0, Payload) ->
|
||||||
Msg = #mqtt_msg{qos = ?QOS_0,
|
Msg = #mqtt_msg{qos = ?QOS_0,
|
||||||
|
@ -1251,19 +1259,33 @@ retry_send(pubrel, PacketId, Now, State = #state{inflight = Inflight}) ->
|
||||||
Error
|
Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
deliver(_Msg, State = #state{request_handler = Hdlr}) when Hdlr =/= ?NO_REQ_HANDLER ->
|
||||||
|
%% message has been terminated by request handler, hence should not continue processing
|
||||||
|
State;
|
||||||
deliver(#mqtt_msg{qos = QoS, dup = Dup, retain = Retain, packet_id = PacketId,
|
deliver(#mqtt_msg{qos = QoS, dup = Dup, retain = Retain, packet_id = PacketId,
|
||||||
topic = Topic, props = Props, payload = Payload},
|
topic = Topic, props = Props, payload = Payload},
|
||||||
State = #state{owner = Owner, request_handler = RequestHandler}) ->
|
State) ->
|
||||||
case RequestHandler of
|
Msg = #{qos => QoS, dup => Dup, retain => Retain, packet_id => PacketId,
|
||||||
?NO_HANDLER ->
|
|
||||||
Owner ! {publish, #{qos => QoS, dup => Dup, retain => Retain, packet_id => PacketId,
|
|
||||||
topic => Topic, properties => Props, payload => Payload,
|
topic => Topic, properties => Props, payload => Payload,
|
||||||
client_pid => self()}};
|
client_pid => self()},
|
||||||
_ ->
|
ok = eval_msg_handler(State, publish, Msg),
|
||||||
ok
|
|
||||||
end,
|
|
||||||
State.
|
State.
|
||||||
|
|
||||||
|
eval_msg_handler(#state{msg_handler = ?NO_REQ_HANDLER,
|
||||||
|
owner = Owner},
|
||||||
|
disconnected, {ReasonCode, Properties}) ->
|
||||||
|
%% Special handling for disconnected message when there is no handler callback
|
||||||
|
Owner ! {disconnected, ReasonCode, Properties},
|
||||||
|
ok;
|
||||||
|
eval_msg_handler(#state{msg_handler = ?NO_REQ_HANDLER,
|
||||||
|
owner = Owner}, Kind, Msg) ->
|
||||||
|
Owner ! {Kind, Msg},
|
||||||
|
ok;
|
||||||
|
eval_msg_handler(#state{msg_handler = Handler}, Kind, Msg) ->
|
||||||
|
F = maps:get(Kind, Handler),
|
||||||
|
_ = F(Msg),
|
||||||
|
ok.
|
||||||
|
|
||||||
packet_to_msg(#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
|
packet_to_msg(#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
|
||||||
dup = Dup,
|
dup = Dup,
|
||||||
qos = QoS,
|
qos = QoS,
|
||||||
|
|
Loading…
Reference in New Issue