fix issue #292 - async sub/unsub
This commit is contained in:
parent
9f643ea267
commit
d5a400c308
|
@ -34,7 +34,10 @@
|
||||||
-include("emqttd_protocol.hrl").
|
-include("emqttd_protocol.hrl").
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/2, session/1, info/1, kick/1, subscribe/2]).
|
-export([start_link/2, session/1, info/1, kick/1]).
|
||||||
|
|
||||||
|
%% SUB/UNSUB Asynchronously
|
||||||
|
-export([subscribe/2, unsubscribe/2]).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
@ -59,7 +62,7 @@ start_link(SockArgs, MqttEnv) ->
|
||||||
{ok, proc_lib:spawn_link(?MODULE, init, [[SockArgs, MqttEnv]])}.
|
{ok, proc_lib:spawn_link(?MODULE, init, [[SockArgs, MqttEnv]])}.
|
||||||
|
|
||||||
session(CPid) ->
|
session(CPid) ->
|
||||||
gen_server:call(CPid, session).
|
gen_server:call(CPid, session, infinity).
|
||||||
|
|
||||||
info(CPid) ->
|
info(CPid) ->
|
||||||
gen_server:call(CPid, info, infinity).
|
gen_server:call(CPid, info, infinity).
|
||||||
|
@ -70,6 +73,9 @@ kick(CPid) ->
|
||||||
subscribe(CPid, TopicTable) ->
|
subscribe(CPid, TopicTable) ->
|
||||||
gen_server:cast(CPid, {subscribe, TopicTable}).
|
gen_server:cast(CPid, {subscribe, TopicTable}).
|
||||||
|
|
||||||
|
unsubscribe(CPid, Topics) ->
|
||||||
|
gen_server:cast(CPid, {unsubscribe, Topics}).
|
||||||
|
|
||||||
init([SockArgs = {Transport, Sock, _SockFun}, MqttEnv]) ->
|
init([SockArgs = {Transport, Sock, _SockFun}, MqttEnv]) ->
|
||||||
% Transform if ssl.
|
% Transform if ssl.
|
||||||
{ok, NewSock} = esockd_connection:accept(SockArgs),
|
{ok, NewSock} = esockd_connection:accept(SockArgs),
|
||||||
|
@ -107,9 +113,11 @@ handle_call(Req, _From, State = #state{peername = Peername}) ->
|
||||||
lager:critical("Client ~s: unexpected request - ~p", [emqttd_net:format(Peername), Req]),
|
lager:critical("Client ~s: unexpected request - ~p", [emqttd_net:format(Peername), Req]),
|
||||||
{reply, {error, unsupported_request}, State}.
|
{reply, {error, unsupported_request}, State}.
|
||||||
|
|
||||||
handle_cast({subscribe, TopicTable}, State = #state{proto_state = ProtoState}) ->
|
handle_cast({subscribe, TopicTable}, State) ->
|
||||||
{ok, ProtoState1} = emqttd_protocol:handle({subscribe, TopicTable}, ProtoState),
|
with_session(fun(SessPid) -> emqttd_session:subscribe(SessPid, TopicTable) end, State);
|
||||||
noreply(State#state{proto_state = ProtoState1});
|
|
||||||
|
handle_cast({unsubscribe, Topics}, State) ->
|
||||||
|
with_session(fun(SessPid) -> emqttd_session:unsubscribe(SessPid, Topics) end, State);
|
||||||
|
|
||||||
handle_cast(Msg, State = #state{peername = Peername}) ->
|
handle_cast(Msg, State = #state{peername = Peername}) ->
|
||||||
lager:critical("Client ~s: unexpected msg - ~p",[emqttd_net:format(Peername), Msg]),
|
lager:critical("Client ~s: unexpected msg - ~p",[emqttd_net:format(Peername), Msg]),
|
||||||
|
@ -149,17 +157,26 @@ handle_info({inet_reply, _Sock, {error, Reason}}, State = #state{peername = Peer
|
||||||
|
|
||||||
handle_info({keepalive, start, TimeoutSec}, State = #state{transport = Transport, socket = Socket, peername = Peername}) ->
|
handle_info({keepalive, start, TimeoutSec}, State = #state{transport = Transport, socket = Socket, peername = Peername}) ->
|
||||||
lager:debug("Client ~s: Start KeepAlive with ~p seconds", [emqttd_net:format(Peername), TimeoutSec]),
|
lager:debug("Client ~s: Start KeepAlive with ~p seconds", [emqttd_net:format(Peername), TimeoutSec]),
|
||||||
KeepAlive = emqttd_keepalive:new({Transport, Socket}, TimeoutSec, {keepalive, timeout}),
|
StatFun = fun() ->
|
||||||
|
case Transport:getstat(Socket, [recv_oct]) of
|
||||||
|
{ok, [{recv_oct, RecvOct}]} -> {ok, RecvOct};
|
||||||
|
{error, Error} -> {error, Error}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
KeepAlive = emqttd_keepalive:start(StatFun, TimeoutSec, {keepalive, check}),
|
||||||
noreply(State#state{keepalive = KeepAlive});
|
noreply(State#state{keepalive = KeepAlive});
|
||||||
|
|
||||||
handle_info({keepalive, timeout}, State = #state{peername = Peername, keepalive = KeepAlive}) ->
|
handle_info({keepalive, check}, State = #state{peername = Peername, keepalive = KeepAlive}) ->
|
||||||
case emqttd_keepalive:resume(KeepAlive) of
|
case emqttd_keepalive:check(KeepAlive) of
|
||||||
timeout ->
|
{ok, KeepAlive1} ->
|
||||||
|
lager:debug("Client ~s: Keepalive Resumed", [emqttd_net:format(Peername)]),
|
||||||
|
noreply(State#state{keepalive = KeepAlive1});
|
||||||
|
{error, timeout} ->
|
||||||
lager:debug("Client ~s: Keepalive Timeout!", [emqttd_net:format(Peername)]),
|
lager:debug("Client ~s: Keepalive Timeout!", [emqttd_net:format(Peername)]),
|
||||||
stop({shutdown, keepalive_timeout}, State#state{keepalive = undefined});
|
stop({shutdown, keepalive_timeout}, State#state{keepalive = undefined});
|
||||||
{resumed, KeepAlive1} ->
|
{error, Error} ->
|
||||||
lager:debug("Client ~s: Keepalive Resumed", [emqttd_net:format(Peername)]),
|
lager:debug("Client ~s: Keepalive Error: ~p!", [emqttd_net:format(Peername), Error]),
|
||||||
noreply(State#state{keepalive = KeepAlive1})
|
stop({shutdown, keepalive_error}, State#state{keepalive = undefined})
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info(Info, State = #state{peername = Peername}) ->
|
handle_info(Info, State = #state{peername = Peername}) ->
|
||||||
|
@ -188,12 +205,20 @@ terminate(Reason, #state{peername = Peername,
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
|
%%%=============================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%=============================================================================
|
||||||
|
|
||||||
noreply(State) ->
|
noreply(State) ->
|
||||||
{noreply, State, hibernate}.
|
{noreply, State, hibernate}.
|
||||||
|
|
||||||
%-------------------------------------------------------
|
stop(Reason, State) ->
|
||||||
% receive and parse tcp data
|
{stop, Reason, State}.
|
||||||
%-------------------------------------------------------
|
|
||||||
|
with_session(Fun, State = #state{proto_state = ProtoState}) ->
|
||||||
|
Fun(emqttd_protocol:session(ProtoState)), noreply(State).
|
||||||
|
|
||||||
|
%% receive and parse tcp data
|
||||||
received(<<>>, State) ->
|
received(<<>>, State) ->
|
||||||
{noreply, State, hibernate};
|
{noreply, State, hibernate};
|
||||||
|
|
||||||
|
@ -244,12 +269,8 @@ control_throttle(State = #state{conn_state = Flow,
|
||||||
{_, _} -> run_socket(State)
|
{_, _} -> run_socket(State)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
stop(Reason, State) ->
|
|
||||||
{stop, Reason, State}.
|
|
||||||
|
|
||||||
received_stats(?PACKET(Type)) ->
|
received_stats(?PACKET(Type)) ->
|
||||||
emqttd_metrics:inc('packets/received'),
|
emqttd_metrics:inc('packets/received'), inc(Type).
|
||||||
inc(Type).
|
|
||||||
inc(?CONNECT) ->
|
inc(?CONNECT) ->
|
||||||
emqttd_metrics:inc('packets/connect');
|
emqttd_metrics:inc('packets/connect');
|
||||||
inc(?PUBLISH) ->
|
inc(?PUBLISH) ->
|
||||||
|
|
|
@ -239,16 +239,11 @@ handle(?SUBSCRIBE_PACKET(PacketId, TopicTable), State = #proto_state{client_id =
|
||||||
case lists:member(deny, AllowDenies) of
|
case lists:member(deny, AllowDenies) of
|
||||||
true ->
|
true ->
|
||||||
%%TODO: return 128 QoS when deny... no need to SUBACK?
|
%%TODO: return 128 QoS when deny... no need to SUBACK?
|
||||||
lager:error("SUBSCRIBE from '~s' Denied: ~p", [ClientId, TopicTable]),
|
lager:error("SUBSCRIBE from '~s' Denied: ~p", [ClientId, TopicTable]);
|
||||||
{ok, State};
|
|
||||||
false ->
|
false ->
|
||||||
%%TODO: GrantedQos should be renamed.
|
Callback = fun(GrantedQos) -> send(?SUBACK_PACKET(PacketId, GrantedQos), State) end,
|
||||||
{ok, GrantedQos} = emqttd_session:subscribe(Session, TopicTable),
|
emqttd_session:subscribe(Session, TopicTable, Callback)
|
||||||
send(?SUBACK_PACKET(PacketId, GrantedQos), State)
|
end,
|
||||||
end;
|
|
||||||
|
|
||||||
handle({subscribe, TopicTable}, State = #proto_state{session = Session}) ->
|
|
||||||
{ok, _GrantedQos} = emqttd_session:subscribe(Session, TopicTable),
|
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
|
||||||
%% protect from empty topic list
|
%% protect from empty topic list
|
||||||
|
@ -256,7 +251,7 @@ handle(?UNSUBSCRIBE_PACKET(PacketId, []), State) ->
|
||||||
send(?UNSUBACK_PACKET(PacketId), State);
|
send(?UNSUBACK_PACKET(PacketId), State);
|
||||||
|
|
||||||
handle(?UNSUBSCRIBE_PACKET(PacketId, Topics), State = #proto_state{session = Session}) ->
|
handle(?UNSUBSCRIBE_PACKET(PacketId, Topics), State = #proto_state{session = Session}) ->
|
||||||
ok = emqttd_session:unsubscribe(Session, Topics),
|
emqttd_session:unsubscribe(Session, Topics),
|
||||||
send(?UNSUBACK_PACKET(PacketId), State);
|
send(?UNSUBACK_PACKET(PacketId), State);
|
||||||
|
|
||||||
handle(?PACKET(?PINGREQ), State) ->
|
handle(?PACKET(?PINGREQ), State) ->
|
||||||
|
@ -349,7 +344,7 @@ send_willmsg(ClientId, WillMsg) ->
|
||||||
start_keepalive(0) -> ignore;
|
start_keepalive(0) -> ignore;
|
||||||
|
|
||||||
start_keepalive(Sec) when Sec > 0 ->
|
start_keepalive(Sec) when Sec > 0 ->
|
||||||
self() ! {keepalive, start, round(Sec * 1.5)}.
|
self() ! {keepalive, start, round(Sec * 1.2)}.
|
||||||
|
|
||||||
%%----------------------------------------------------------------------------
|
%%----------------------------------------------------------------------------
|
||||||
%% Validate Packets
|
%% Validate Packets
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
%% PubSub APIs
|
%% PubSub APIs
|
||||||
-export([publish/2,
|
-export([publish/2,
|
||||||
puback/2, pubrec/2, pubrel/2, pubcomp/2,
|
puback/2, pubrec/2, pubrel/2, pubcomp/2,
|
||||||
subscribe/2, unsubscribe/2]).
|
subscribe/2, subscribe/3, unsubscribe/2]).
|
||||||
|
|
||||||
-behaviour(gen_server2).
|
-behaviour(gen_server2).
|
||||||
|
|
||||||
|
@ -166,9 +166,13 @@ destroy(SessPid, ClientId) ->
|
||||||
%% @doc Subscribe Topics
|
%% @doc Subscribe Topics
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec subscribe(pid(), [{binary(), mqtt_qos()}]) -> {ok, [mqtt_qos()]}.
|
-spec subscribe(pid(), [{binary(), mqtt_qos()}]) -> ok.
|
||||||
subscribe(SessPid, TopicTable) ->
|
subscribe(SessPid, TopicTable) ->
|
||||||
gen_server2:call(SessPid, {subscribe, TopicTable}, ?PUBSUB_TIMEOUT).
|
subscribe(SessPid, TopicTable, fun(_) -> ok end).
|
||||||
|
|
||||||
|
-spec subscribe(pid(), [{binary(), mqtt_qos()}], Callback :: fun()) -> ok.
|
||||||
|
subscribe(SessPid, TopicTable, Callback) ->
|
||||||
|
gen_server2:cast(SessPid, {subscribe, TopicTable, Callback}).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Publish message
|
%% @doc Publish message
|
||||||
|
@ -213,7 +217,7 @@ pubcomp(SessPid, PktId) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec unsubscribe(pid(), [binary()]) -> ok.
|
-spec unsubscribe(pid(), [binary()]) -> ok.
|
||||||
unsubscribe(SessPid, Topics) ->
|
unsubscribe(SessPid, Topics) ->
|
||||||
gen_server2:call(SessPid, {unsubscribe, Topics}, ?PUBSUB_TIMEOUT).
|
gen_server2:cast(SessPid, {unsubscribe, Topics}).
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% gen_server callbacks
|
%%% gen_server callbacks
|
||||||
|
@ -247,11 +251,7 @@ init([CleanSess, ClientId, ClientPid]) ->
|
||||||
{ok, start_collector(Session#session{client_mon = MRef}), hibernate}.
|
{ok, start_collector(Session#session{client_mon = MRef}), hibernate}.
|
||||||
|
|
||||||
prioritise_call(Msg, _From, _Len, _State) ->
|
prioritise_call(Msg, _From, _Len, _State) ->
|
||||||
case Msg of
|
case Msg of _ -> 0 end.
|
||||||
{unsubscribe, _} -> 2;
|
|
||||||
{subscribe, _} -> 1;
|
|
||||||
_ -> 0
|
|
||||||
end.
|
|
||||||
|
|
||||||
prioritise_cast(Msg, _Len, _State) ->
|
prioritise_cast(Msg, _Len, _State) ->
|
||||||
case Msg of
|
case Msg of
|
||||||
|
@ -261,12 +261,14 @@ prioritise_cast(Msg, _Len, _State) ->
|
||||||
{pubcomp, _PktId} -> 8;
|
{pubcomp, _PktId} -> 8;
|
||||||
{pubrec, _PktId} -> 8;
|
{pubrec, _PktId} -> 8;
|
||||||
{puback, _PktId} -> 7;
|
{puback, _PktId} -> 7;
|
||||||
|
{unsubscribe, _, _} -> 6;
|
||||||
|
{subscribe, _, _} -> 5;
|
||||||
_ -> 0
|
_ -> 0
|
||||||
end.
|
end.
|
||||||
|
|
||||||
prioritise_info(Msg, _Len, _State) ->
|
prioritise_info(Msg, _Len, _State) ->
|
||||||
case Msg of
|
case Msg of
|
||||||
{'DOWN', _, process, _, _} -> 10;
|
{'DOWN', _, _, _, _} -> 10;
|
||||||
{'EXIT', _, _} -> 10;
|
{'EXIT', _, _} -> 10;
|
||||||
session_expired -> 10;
|
session_expired -> 10;
|
||||||
{timeout, _, _} -> 5;
|
{timeout, _, _} -> 5;
|
||||||
|
@ -275,17 +277,40 @@ prioritise_info(Msg, _Len, _State) ->
|
||||||
_ -> 0
|
_ -> 0
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle_call({subscribe, TopicTable0}, _From, Session = #session{client_id = ClientId,
|
handle_call({publish, Msg = #mqtt_message{qos = ?QOS_2, pktid = PktId}}, _From,
|
||||||
subscriptions = Subscriptions}) ->
|
Session = #session{client_id = ClientId,
|
||||||
|
awaiting_rel = AwaitingRel,
|
||||||
|
await_rel_timeout = Timeout}) ->
|
||||||
|
case check_awaiting_rel(Session) of
|
||||||
|
true ->
|
||||||
|
TRef = timer(Timeout, {timeout, awaiting_rel, PktId}),
|
||||||
|
AwaitingRel1 = maps:put(PktId, {Msg, TRef}, AwaitingRel),
|
||||||
|
{reply, ok, Session#session{awaiting_rel = AwaitingRel1}};
|
||||||
|
false ->
|
||||||
|
lager:critical([{client, ClientId}], "Session(~s) dropped Qos2 message "
|
||||||
|
"for too many awaiting_rel: ~p", [ClientId, Msg]),
|
||||||
|
{reply, {error, dropped}, Session}
|
||||||
|
end;
|
||||||
|
|
||||||
|
handle_call(Req, _From, State) ->
|
||||||
|
lager:critical("Unexpected Request: ~p", [Req]),
|
||||||
|
{reply, ok, State}.
|
||||||
|
|
||||||
|
handle_cast({subscribe, TopicTable0, Callback}, Session = #session{
|
||||||
|
client_id = ClientId, subscriptions = Subscriptions}) ->
|
||||||
|
|
||||||
case TopicTable0 -- Subscriptions of
|
|
||||||
[] ->
|
|
||||||
{reply, {ok, [Qos || {_, Qos} <- TopicTable0]}, Session};
|
|
||||||
_ ->
|
|
||||||
TopicTable = emqttd_broker:foldl_hooks('client.subscribe', [ClientId], TopicTable0),
|
TopicTable = emqttd_broker:foldl_hooks('client.subscribe', [ClientId], TopicTable0),
|
||||||
|
|
||||||
|
case TopicTable -- Subscriptions of
|
||||||
|
[] ->
|
||||||
|
catch Callback([Qos || {_, Qos} <- TopicTable]),
|
||||||
|
noreply(Session);
|
||||||
|
_ ->
|
||||||
%% subscribe first and don't care if the subscriptions have been existed
|
%% subscribe first and don't care if the subscriptions have been existed
|
||||||
{ok, GrantedQos} = emqttd_pubsub:subscribe(TopicTable),
|
{ok, GrantedQos} = emqttd_pubsub:subscribe(TopicTable),
|
||||||
|
|
||||||
|
catch Callback(GrantedQos),
|
||||||
|
|
||||||
emqttd_broker:foreach_hooks('client.subscribe.after', [ClientId, TopicTable]),
|
emqttd_broker:foreach_hooks('client.subscribe.after', [ClientId, TopicTable]),
|
||||||
|
|
||||||
lager:info([{client, ClientId}], "Session(~s): subscribe ~p, Granted QoS: ~p",
|
lager:info([{client, ClientId}], "Session(~s): subscribe ~p, Granted QoS: ~p",
|
||||||
|
@ -310,10 +335,10 @@ handle_call({subscribe, TopicTable0}, _From, Session = #session{client_id = Clie
|
||||||
[{Topic, Qos} | Acc]
|
[{Topic, Qos} | Acc]
|
||||||
end
|
end
|
||||||
end, Subscriptions, TopicTable),
|
end, Subscriptions, TopicTable),
|
||||||
{reply, {ok, GrantedQos}, Session#session{subscriptions = Subscriptions1}}
|
noreply(Session#session{subscriptions = Subscriptions1})
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_call({unsubscribe, Topics0}, _From, Session = #session{client_id = ClientId,
|
handle_cast({unsubscribe, Topics0}, Session = #session{client_id = ClientId,
|
||||||
subscriptions = Subscriptions}) ->
|
subscriptions = Subscriptions}) ->
|
||||||
|
|
||||||
Topics = emqttd_broker:foldl_hooks('client.unsubscribe', [ClientId], Topics0),
|
Topics = emqttd_broker:foldl_hooks('client.unsubscribe', [ClientId], Topics0),
|
||||||
|
@ -333,26 +358,7 @@ handle_call({unsubscribe, Topics0}, _From, Session = #session{client_id = Client
|
||||||
end
|
end
|
||||||
end, Subscriptions, Topics),
|
end, Subscriptions, Topics),
|
||||||
|
|
||||||
{reply, ok, Session#session{subscriptions = Subscriptions1}};
|
noreply(Session#session{subscriptions = Subscriptions1});
|
||||||
|
|
||||||
handle_call({publish, Msg = #mqtt_message{qos = ?QOS_2, pktid = PktId}}, _From,
|
|
||||||
Session = #session{client_id = ClientId,
|
|
||||||
awaiting_rel = AwaitingRel,
|
|
||||||
await_rel_timeout = Timeout}) ->
|
|
||||||
case check_awaiting_rel(Session) of
|
|
||||||
true ->
|
|
||||||
TRef = timer(Timeout, {timeout, awaiting_rel, PktId}),
|
|
||||||
AwaitingRel1 = maps:put(PktId, {Msg, TRef}, AwaitingRel),
|
|
||||||
{reply, ok, Session#session{awaiting_rel = AwaitingRel1}};
|
|
||||||
false ->
|
|
||||||
lager:critical([{client, ClientId}], "Session(~s) dropped Qos2 message "
|
|
||||||
"for too many awaiting_rel: ~p", [ClientId, Msg]),
|
|
||||||
{reply, {error, dropped}, Session}
|
|
||||||
end;
|
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
|
||||||
lager:critical("Unexpected Request: ~p", [Req]),
|
|
||||||
{reply, ok, State}.
|
|
||||||
|
|
||||||
handle_cast({destroy, ClientId}, Session = #session{client_id = ClientId}) ->
|
handle_cast({destroy, ClientId}, Session = #session{client_id = ClientId}) ->
|
||||||
lager:warning([{client, ClientId}], "Session(~s) destroyed", [ClientId]),
|
lager:warning([{client, ClientId}], "Session(~s) destroyed", [ClientId]),
|
||||||
|
|
|
@ -34,7 +34,10 @@
|
||||||
-include("emqttd_protocol.hrl").
|
-include("emqttd_protocol.hrl").
|
||||||
|
|
||||||
%% API Exports
|
%% API Exports
|
||||||
-export([start_link/1, ws_loop/3, subscribe/2]).
|
-export([start_link/1, ws_loop/3, session/1, info/1, kick/1]).
|
||||||
|
|
||||||
|
%% SUB/UNSUB Asynchronously
|
||||||
|
-export([subscribe/2, unsubscribe/2]).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
@ -61,9 +64,21 @@ start_link(Req) ->
|
||||||
packet_opts = PktOpts,
|
packet_opts = PktOpts,
|
||||||
parser = emqttd_parser:new(PktOpts)}).
|
parser = emqttd_parser:new(PktOpts)}).
|
||||||
|
|
||||||
|
session(CPid) ->
|
||||||
|
gen_server:call(CPid, session, infinity).
|
||||||
|
|
||||||
|
info(CPid) ->
|
||||||
|
gen_server:call(CPid, info, infinity).
|
||||||
|
|
||||||
|
kick(CPid) ->
|
||||||
|
gen_server:call(CPid, kick).
|
||||||
|
|
||||||
subscribe(CPid, TopicTable) ->
|
subscribe(CPid, TopicTable) ->
|
||||||
gen_server:cast(CPid, {subscribe, TopicTable}).
|
gen_server:cast(CPid, {subscribe, TopicTable}).
|
||||||
|
|
||||||
|
unsubscribe(CPid, Topics) ->
|
||||||
|
gen_server:cast(CPid, {unsubscribe, Topics}).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @private
|
%% @private
|
||||||
%% @doc Start WebSocket client.
|
%% @doc Start WebSocket client.
|
||||||
|
@ -112,17 +127,30 @@ init([WsPid, Req, ReplyChannel, PktOpts]) ->
|
||||||
ProtoState = emqttd_protocol:init(Peername, SendFun, [{ws_initial_headers, HeadersList}|PktOpts]),
|
ProtoState = emqttd_protocol:init(Peername, SendFun, [{ws_initial_headers, HeadersList}|PktOpts]),
|
||||||
{ok, #client_state{ws_pid = WsPid, request = Req, proto_state = ProtoState}}.
|
{ok, #client_state{ws_pid = WsPid, request = Req, proto_state = ProtoState}}.
|
||||||
|
|
||||||
|
handle_call(session, _From, State = #client_state{proto_state = ProtoState}) ->
|
||||||
|
{reply, emqttd_protocol:session(ProtoState), State};
|
||||||
|
|
||||||
|
handle_call(info, _From, State = #client_state{request = Req,
|
||||||
|
proto_state = ProtoState}) ->
|
||||||
|
{reply, [{websocket, true}, {peer, Req:get(peer)}
|
||||||
|
| emqttd_protocol:info(ProtoState)], State};
|
||||||
|
|
||||||
|
handle_call(kick, _From, State) ->
|
||||||
|
{stop, {shutdown, kick}, ok, State};
|
||||||
|
|
||||||
handle_call(_Req, _From, State) ->
|
handle_call(_Req, _From, State) ->
|
||||||
{reply, error, State}.
|
{reply, error, State}.
|
||||||
|
|
||||||
handle_cast({subscribe, TopicTable}, State = #client_state{proto_state = ProtoState}) ->
|
handle_cast({subscribe, TopicTable}, State) ->
|
||||||
{ok, ProtoState1} = emqttd_protocol:handle({subscribe, TopicTable}, ProtoState),
|
with_session(fun(SessPid) -> emqttd_session:subscribe(SessPid, TopicTable) end, State);
|
||||||
{noreply, State#client_state{proto_state = ProtoState1}, hibernate};
|
|
||||||
|
handle_cast({unsubscribe, Topics}, State) ->
|
||||||
|
with_session(fun(SessPid) -> emqttd_session:unsubscribe(SessPid, Topics) end, State);
|
||||||
|
|
||||||
handle_cast({received, Packet}, State = #client_state{proto_state = ProtoState}) ->
|
handle_cast({received, Packet}, State = #client_state{proto_state = ProtoState}) ->
|
||||||
case emqttd_protocol:received(Packet, ProtoState) of
|
case emqttd_protocol:received(Packet, ProtoState) of
|
||||||
{ok, ProtoState1} ->
|
{ok, ProtoState1} ->
|
||||||
{noreply, State#client_state{proto_state = ProtoState1}};
|
noreply(State#client_state{proto_state = ProtoState1});
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
lager:error("MQTT protocol error ~p", [Error]),
|
lager:error("MQTT protocol error ~p", [Error]),
|
||||||
stop({shutdown, Error}, State);
|
stop({shutdown, Error}, State);
|
||||||
|
@ -137,11 +165,11 @@ handle_cast(_Msg, State) ->
|
||||||
|
|
||||||
handle_info({deliver, Message}, State = #client_state{proto_state = ProtoState}) ->
|
handle_info({deliver, Message}, State = #client_state{proto_state = ProtoState}) ->
|
||||||
{ok, ProtoState1} = emqttd_protocol:send(Message, ProtoState),
|
{ok, ProtoState1} = emqttd_protocol:send(Message, ProtoState),
|
||||||
{noreply, State#client_state{proto_state = ProtoState1}};
|
noreply(State#client_state{proto_state = ProtoState1});
|
||||||
|
|
||||||
handle_info({redeliver, {?PUBREL, PacketId}}, State = #client_state{proto_state = ProtoState}) ->
|
handle_info({redeliver, {?PUBREL, PacketId}}, State = #client_state{proto_state = ProtoState}) ->
|
||||||
{ok, ProtoState1} = emqttd_protocol:redeliver({?PUBREL, PacketId}, ProtoState),
|
{ok, ProtoState1} = emqttd_protocol:redeliver({?PUBREL, PacketId}, ProtoState),
|
||||||
{noreply, State#client_state{proto_state = ProtoState1}};
|
noreply(State#client_state{proto_state = ProtoState1});
|
||||||
|
|
||||||
handle_info({stop, duplicate_id, _NewPid}, State = #client_state{proto_state = ProtoState}) ->
|
handle_info({stop, duplicate_id, _NewPid}, State = #client_state{proto_state = ProtoState}) ->
|
||||||
lager:error("Shutdown for duplicate clientid: ~s", [emqttd_protocol:clientid(ProtoState)]),
|
lager:error("Shutdown for duplicate clientid: ~s", [emqttd_protocol:clientid(ProtoState)]),
|
||||||
|
@ -149,18 +177,27 @@ handle_info({stop, duplicate_id, _NewPid}, State = #client_state{proto_state = P
|
||||||
|
|
||||||
handle_info({keepalive, start, TimeoutSec}, State = #client_state{request = Req}) ->
|
handle_info({keepalive, start, TimeoutSec}, State = #client_state{request = Req}) ->
|
||||||
lager:debug("Client(WebSocket) ~s: Start KeepAlive with ~p seconds", [Req:get(peer), TimeoutSec]),
|
lager:debug("Client(WebSocket) ~s: Start KeepAlive with ~p seconds", [Req:get(peer), TimeoutSec]),
|
||||||
KeepAlive = emqttd_keepalive:new({esockd_transport, Req:get(socket)},
|
Socket = Req:get(socket),
|
||||||
TimeoutSec, {keepalive, timeout}),
|
StatFun = fun() ->
|
||||||
{noreply, State#client_state{keepalive = KeepAlive}};
|
case esockd_transport:getstat(Socket, [recv_oct]) of
|
||||||
|
{ok, [{recv_oct, RecvOct}]} -> {ok, RecvOct};
|
||||||
|
{error, Error} -> {error, Error}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
KeepAlive = emqttd_keepalive:start(StatFun, TimeoutSec, {keepalive, check}),
|
||||||
|
noreply(State#client_state{keepalive = KeepAlive});
|
||||||
|
|
||||||
handle_info({keepalive, timeout}, State = #client_state{request = Req, keepalive = KeepAlive}) ->
|
handle_info({keepalive, check}, State = #client_state{request = Req, keepalive = KeepAlive}) ->
|
||||||
case emqttd_keepalive:resume(KeepAlive) of
|
case emqttd_keepalive:check(KeepAlive) of
|
||||||
timeout ->
|
{ok, KeepAlive1} ->
|
||||||
|
lager:debug("Client(WebSocket) ~s: Keepalive Resumed", [Req:get(peer)]),
|
||||||
|
noreply(State#client_state{keepalive = KeepAlive1});
|
||||||
|
{error, timeout} ->
|
||||||
lager:debug("Client(WebSocket) ~s: Keepalive Timeout!", [Req:get(peer)]),
|
lager:debug("Client(WebSocket) ~s: Keepalive Timeout!", [Req:get(peer)]),
|
||||||
stop({shutdown, keepalive_timeout}, State#client_state{keepalive = undefined});
|
stop({shutdown, keepalive_timeout}, State#client_state{keepalive = undefined});
|
||||||
{resumed, KeepAlive1} ->
|
{error, Error} ->
|
||||||
lager:debug("Client(WebSocket) ~s: Keepalive Resumed", [Req:get(peer)]),
|
lager:debug("Client(WebSocket) ~s: Keepalive Error: ~p", [Req:get(peer), Error]),
|
||||||
{noreply, State#client_state{keepalive = KeepAlive1}}
|
stop({shutdown, keepalive_error}, State#client_state{keepalive = undefined})
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info({'EXIT', WsPid, Reason}, State = #client_state{ws_pid = WsPid, proto_state = ProtoState}) ->
|
handle_info({'EXIT', WsPid, Reason}, State = #client_state{ws_pid = WsPid, proto_state = ProtoState}) ->
|
||||||
|
@ -170,7 +207,7 @@ handle_info({'EXIT', WsPid, Reason}, State = #client_state{ws_pid = WsPid, proto
|
||||||
|
|
||||||
handle_info(Info, State = #client_state{request = Req}) ->
|
handle_info(Info, State = #client_state{request = Req}) ->
|
||||||
lager:critical("Client(WebSocket) ~s: Unexpected Info - ~p", [Req:get(peer), Info]),
|
lager:critical("Client(WebSocket) ~s: Unexpected Info - ~p", [Req:get(peer), Info]),
|
||||||
{noreply, State}.
|
noreply(State).
|
||||||
|
|
||||||
terminate(Reason, #client_state{proto_state = ProtoState, keepalive = KeepAlive}) ->
|
terminate(Reason, #client_state{proto_state = ProtoState, keepalive = KeepAlive}) ->
|
||||||
lager:info("WebSocket client terminated: ~p", [Reason]),
|
lager:info("WebSocket client terminated: ~p", [Reason]),
|
||||||
|
@ -189,6 +226,12 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
|
noreply(State) ->
|
||||||
|
{noreply, State, hibernate}.
|
||||||
|
|
||||||
stop(Reason, State ) ->
|
stop(Reason, State ) ->
|
||||||
{stop, Reason, State}.
|
{stop, Reason, State}.
|
||||||
|
|
||||||
|
with_session(Fun, State = #client_state{proto_state = ProtoState}) ->
|
||||||
|
Fun(emqttd_protocol:session(ProtoState)), noreply(State).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue