fix(gw): fix the subscription apis bugs

This commit is contained in:
JianBo He 2021-09-01 11:42:46 +08:00
parent 1748de5ee3
commit 40d34ccd85
5 changed files with 78 additions and 73 deletions

View File

@ -120,13 +120,22 @@ clients_insta(delete, #{ bindings := #{name := GwName0,
emqx_gateway_http:kickout_client(GwName, ClientId), emqx_gateway_http:kickout_client(GwName, ClientId),
{200}. {200}.
%% FIXME:
%% List the subscription without mountpoint, but has SubOpts,
%% for example, share group ...
subscriptions(get, #{ bindings := #{name := GwName0, subscriptions(get, #{ bindings := #{name := GwName0,
clientid := ClientId0} clientid := ClientId0}
}) -> }) ->
GwName = binary_to_existing_atom(GwName0), GwName = binary_to_existing_atom(GwName0),
ClientId = emqx_mgmt_util:urldecode(ClientId0), ClientId = emqx_mgmt_util:urldecode(ClientId0),
{200, emqx_gateway_http:list_client_subscriptions(GwName, ClientId)}; case emqx_gateway_http:list_client_subscriptions(GwName, ClientId) of
{error, Reason} ->
return_http_error(404, Reason);
{ok, Subs} ->
{200, Subs}
end;
%% Create the subscription without mountpoint
subscriptions(post, #{ bindings := #{name := GwName0, subscriptions(post, #{ bindings := #{name := GwName0,
clientid := ClientId0}, clientid := ClientId0},
body := Body body := Body
@ -147,6 +156,7 @@ subscriptions(post, #{ bindings := #{name := GwName0,
end end
end; end;
%% Remove the subscription without mountpoint
subscriptions(delete, #{ bindings := #{name := GwName0, subscriptions(delete, #{ bindings := #{name := GwName0,
clientid := ClientId0, clientid := ClientId0,
topic := Topic0 topic := Topic0
@ -166,10 +176,10 @@ subopts(Req) ->
, rap => maps:get(<<"rap">>, Req, 0) , rap => maps:get(<<"rap">>, Req, 0)
, nl => maps:get(<<"nl">>, Req, 0) , nl => maps:get(<<"nl">>, Req, 0)
, rh => maps:get(<<"rh">>, Req, 0) , rh => maps:get(<<"rh">>, Req, 0)
, sub_prop => extra_sub_prop(maps:get(<<"sub_prop">>, Req, #{})) , sub_props => extra_sub_props(maps:get(<<"sub_props">>, Req, #{}))
}. }.
extra_sub_prop(Props) -> extra_sub_props(Props) ->
maps:filter( maps:filter(
fun(_, V) -> V =/= undefined end, fun(_, V) -> V =/= undefined end,
#{subid => maps:get(<<"subid">>, Props, undefined)} #{subid => maps:get(<<"subid">>, Props, undefined)}
@ -595,7 +605,7 @@ properties_client() ->
]). ]).
properties_subscription() -> properties_subscription() ->
ExtraProps = [ {subid, integer, ExtraProps = [ {subid, string,
<<"Only stomp protocol, an uniquely identity for " <<"Only stomp protocol, an uniquely identity for "
"the subscription. range: 1-65535.">>} "the subscription. range: 1-65535.">>}
], ],
@ -610,5 +620,5 @@ properties_subscription() ->
<<"Retain as Published option, enum: 0, 1">>} <<"Retain as Published option, enum: 0, 1">>}
, {rh, integer, , {rh, integer,
<<"Retain Handling option, enum: 0, 1, 2">>} <<"Retain Handling option, enum: 0, 1, 2">>}
, {sub_prop, object, ExtraProps} , {sub_props, object, ExtraProps}
]). ]).

View File

@ -48,6 +48,10 @@
, connection_closed/2 , connection_closed/2
]). ]).
-export([ with_channel/3
, lookup_channels/2
]).
%% Internal funcs for getting tabname by GatewayId %% Internal funcs for getting tabname by GatewayId
-export([cmtabs/1, tabname/2]). -export([cmtabs/1, tabname/2]).

View File

@ -141,63 +141,44 @@ kickout_client(Node, GwName, ClientId) ->
| {ok, list()}. | {ok, list()}.
list_client_subscriptions(GwName, ClientId) -> list_client_subscriptions(GwName, ClientId) ->
%% Get the subscriptions from session-info %% Get the subscriptions from session-info
case emqx_gateway_cm:get_chan_info(GwName, ClientId) of with_channel(GwName, ClientId,
undefined -> fun(Pid) ->
{error, not_found}; Subs = emqx_gateway_conn:call(
Infos -> Pid,
Subs = maps:get(subscriptions, Infos, #{}), subscriptions, ?DEFAULT_CALL_TIMEOUT),
maps:fold(fun(K, V, Acc) -> {ok, lists:map(fun({Topic, SubOpts}) ->
[maps:merge( SubOpts#{topic => Topic}
#{topic => K}, end, Subs)}
maps:with([qos, nl, rap, rh], V)) end).
|Acc]
end, [], Subs)
end.
-spec client_subscribe(gateway_name(), emqx_type:clientid(), -spec client_subscribe(gateway_name(), emqx_type:clientid(),
emqx_type:topic(), emqx_type:subopts()) emqx_type:topic(), emqx_type:subopts())
-> {error, any()} -> {error, any()}
| ok. | ok.
client_subscribe(GwName, ClientId, Topic, SubOpts) -> client_subscribe(GwName, ClientId, Topic, SubOpts) ->
case emqx_gateway_cm:lookup_channels(GwName, ClientId) of with_channel(GwName, ClientId,
[] -> {error, not_found}; fun(Pid) ->
[Pid] ->
%% fixed conn module?
emqx_gateway_conn:call( emqx_gateway_conn:call(
Pid, {subscribe, Topic, SubOpts}, Pid, {subscribe, Topic, SubOpts},
?DEFAULT_CALL_TIMEOUT ?DEFAULT_CALL_TIMEOUT
); )
Pids -> end).
?LOG(warning, "More than one client process ~p was found "
"clientid ~s", [Pids, ClientId]),
_ = [
emqx_gateway_conn:call(
Pid, {subscribe, Topic, SubOpts},
?DEFAULT_CALL_TIMEOUT
) || Pid <- Pids],
ok
end.
-spec client_unsubscribe(gateway_name(), -spec client_unsubscribe(gateway_name(),
emqx_type:clientid(), emqx_type:topic()) emqx_type:clientid(), emqx_type:topic())
-> {error, any()} -> {error, any()}
| ok. | ok.
client_unsubscribe(GwName, ClientId, Topic) -> client_unsubscribe(GwName, ClientId, Topic) ->
case emqx_gateway_cm:lookup_channels(GwName, ClientId) of with_channel(GwName, ClientId,
[] -> {error, not_found}; fun(Pid) ->
[Pid] ->
emqx_gateway_conn:call( emqx_gateway_conn:call(
Pid, {unsubscribe, Topic}, Pid, {unsubscribe, Topic}, ?DEFAULT_CALL_TIMEOUT)
?DEFAULT_CALL_TIMEOUT); end).
Pids ->
?LOG(warning, "More than one client process ~p was found " with_channel(GwName, ClientId, Fun) ->
"clientid ~s", [Pids, ClientId]), case emqx_gateway_cm:with_channel(GwName, ClientId, Fun) of
_ = [ undefined -> {error, not_found};
emqx_gateway_conn:call( Res -> Res
Pid, {unsubscribe, Topic},
?DEFAULT_CALL_TIMEOUT
) || Pid <- Pids],
ok
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -206,10 +187,11 @@ client_unsubscribe(GwName, ClientId, Topic) ->
-spec return_http_error(integer(), binary()) -> binary(). -spec return_http_error(integer(), binary()) -> binary().
return_http_error(Code, Msg) -> return_http_error(Code, Msg) ->
emqx_json:encode( {Code, emqx_json:encode(
#{code => codestr(Code), #{code => codestr(Code),
reason => emqx_gateway_utils:stringfy(Msg) reason => emqx_gateway_utils:stringfy(Msg)
}). })
}.
codestr(404) -> 'RESOURCE_NOT_FOUND'; codestr(404) -> 'RESOURCE_NOT_FOUND';
codestr(401) -> 'NOT_SUPPORTED_NOW'; codestr(401) -> 'NOT_SUPPORTED_NOW';

View File

@ -209,5 +209,6 @@ default_subopts() ->
#{rh => 0, %% Retain Handling #{rh => 0, %% Retain Handling
rap => 0, %% Retain as Publish rap => 0, %% Retain as Publish
nl => 0, %% No Local nl => 0, %% No Local
qos => 0 %% QoS qos => 0, %% QoS
is_new => true
}. }.

View File

@ -393,11 +393,9 @@ handle_in(?PACKET(?CMD_SUBSCRIBE, Headers),
[] -> [] ->
ErrMsg = "Permission denied", ErrMsg = "Permission denied",
handle_out(error, {receipt_id(Headers), ErrMsg}, Channel); handle_out(error, {receipt_id(Headers), ErrMsg}, Channel);
[MountedTopic|_] -> [{MountedTopic, SubOpts}|_] ->
NChannel1 = NChannel#channel{ NSubs = [{SubId, MountedTopic, Ack, SubOpts}|Subs],
subscriptions = [{SubId, MountedTopic, Ack} NChannel1 = NChannel#channel{subscriptions = NSubs},
| Subs]
},
handle_out(receipt, receipt_id(Headers), NChannel1) handle_out(receipt, receipt_id(Headers), NChannel1)
end; end;
{error, ErrMsg, NChannel} -> {error, ErrMsg, NChannel} ->
@ -415,7 +413,7 @@ handle_in(?PACKET(?CMD_UNSUBSCRIBE, Headers),
SubId = header(<<"id">>, Headers), SubId = header(<<"id">>, Headers),
{ok, NChannel} = {ok, NChannel} =
case lists:keyfind(SubId, 1, Subs) of case lists:keyfind(SubId, 1, Subs) of
{SubId, MountedTopic, _Ack} -> {SubId, MountedTopic, _Ack, _SubOpts} ->
Topic = emqx_mountpoint:unmount(Mountpoint, MountedTopic), Topic = emqx_mountpoint:unmount(Mountpoint, MountedTopic),
%% XXX: eval the return topics? %% XXX: eval the return topics?
_ = run_hooks(Ctx, 'client.unsubscribe', _ = run_hooks(Ctx, 'client.unsubscribe',
@ -539,15 +537,16 @@ trans_pipeline([{Func, Args}|More], Outgoings, Channel) ->
%% Subs %% Subs
parse_topic_filter({SubId, Topic}, Channel) -> parse_topic_filter({SubId, Topic}, Channel) ->
TopicFilter = emqx_topic:parse(Topic), {ParsedTopic, SubOpts} = emqx_topic:parse(Topic),
{ok, {SubId, TopicFilter}, Channel}. NSubOpts = SubOpts#{sub_props => #{subid => SubId}},
{ok, {SubId, {ParsedTopic, NSubOpts}}, Channel}.
check_subscribed_status({SubId, TopicFilter}, check_subscribed_status({SubId, {ParsedTopic, _SubOpts}},
#channel{ #channel{
subscriptions = Subs, subscriptions = Subs,
clientinfo = #{mountpoint := Mountpoint} clientinfo = #{mountpoint := Mountpoint}
}) -> }) ->
MountedTopic = emqx_mountpoint:mount(Mountpoint, TopicFilter), MountedTopic = emqx_mountpoint:mount(Mountpoint, ParsedTopic),
case lists:keyfind(SubId, 1, Subs) of case lists:keyfind(SubId, 1, Subs) of
{SubId, MountedTopic, _Ack} -> {SubId, MountedTopic, _Ack} ->
ok; ok;
@ -557,11 +556,11 @@ check_subscribed_status({SubId, TopicFilter},
ok ok
end. end.
check_sub_acl({_SubId, TopicFilter}, check_sub_acl({_SubId, {ParsedTopic, _SubOpts}},
#channel{ #channel{
ctx = Ctx, ctx = Ctx,
clientinfo = ClientInfo}) -> clientinfo = ClientInfo}) ->
case emqx_gateway_ctx:authorize(Ctx, ClientInfo, subscribe, TopicFilter) of case emqx_gateway_ctx:authorize(Ctx, ClientInfo, subscribe, ParsedTopic) of
deny -> {error, "ACL Deny"}; deny -> {error, "ACL Deny"};
allow -> ok allow -> ok
end. end.
@ -571,17 +570,17 @@ do_subscribe(TopicFilters, Channel) ->
do_subscribe([], _Channel, Acc) -> do_subscribe([], _Channel, Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
do_subscribe([{TopicFilter, Option}|More], do_subscribe([{ParsedTopic, SubOpts0}|More],
Channel = #channel{ Channel = #channel{
ctx = Ctx, ctx = Ctx,
clientinfo = ClientInfo clientinfo = ClientInfo
= #{clientid := ClientId, = #{clientid := ClientId,
mountpoint := Mountpoint}}, Acc) -> mountpoint := Mountpoint}}, Acc) ->
SubOpts = maps:merge(emqx_gateway_utils:default_subopts(), Option), SubOpts = maps:merge(emqx_gateway_utils:default_subopts(), SubOpts0),
MountedTopic = emqx_mountpoint:mount(Mountpoint, TopicFilter), MountedTopic = emqx_mountpoint:mount(Mountpoint, ParsedTopic),
_ = emqx_broker:subscribe(MountedTopic, ClientId, SubOpts), _ = emqx_broker:subscribe(MountedTopic, ClientId, SubOpts),
run_hooks(Ctx, 'session.subscribed', [ClientInfo, MountedTopic, SubOpts]), run_hooks(Ctx, 'session.subscribed', [ClientInfo, MountedTopic, SubOpts]),
do_subscribe(More, Channel, [MountedTopic|Acc]). do_subscribe(More, Channel, [{MountedTopic, SubOpts}|Acc]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle outgoing packet %% Handle outgoing packet
@ -631,7 +630,7 @@ handle_call({subscribe, Topic, SubOpts},
subscriptions = Subs subscriptions = Subs
}) -> }) ->
case maps:get(subid, case maps:get(subid,
maps:get(sub_prop, SubOpts, #{}), maps:get(sub_props, SubOpts, #{}),
undefined) of undefined) of
undefined -> undefined ->
reply({error, no_subid}, Channel); reply({error, no_subid}, Channel);
@ -641,11 +640,12 @@ handle_call({subscribe, Topic, SubOpts},
, fun check_subscribed_status/2 , fun check_subscribed_status/2
], {SubId, {Topic, SubOpts}}, Channel) of ], {SubId, {Topic, SubOpts}}, Channel) of
{ok, {_, TopicFilter}, NChannel} -> {ok, {_, TopicFilter}, NChannel} ->
[MountedTopic] = do_subscribe([TopicFilter], NChannel), [{MountedTopic, NSubOpts}] = do_subscribe(
NChannel1 = NChannel#channel{ [TopicFilter],
subscriptions = NChannel
[{SubId, MountedTopic, <<"auto">>}|Subs] ),
}, NSubs = [{SubId, MountedTopic, <<"auto">>, NSubOpts}|Subs],
NChannel1 = NChannel#channel{subscriptions = NSubs},
reply(ok, NChannel1); reply(ok, NChannel1);
{error, ErrMsg, NChannel} -> {error, ErrMsg, NChannel} ->
?LOG(error, "Failed to subscribe topic ~s, reason: ~s", ?LOG(error, "Failed to subscribe topic ~s, reason: ~s",
@ -670,6 +670,14 @@ handle_call({unsubscribe, Topic},
subscriptions = lists:keydelete(MountedTopic, 2, Subs)} subscriptions = lists:keydelete(MountedTopic, 2, Subs)}
); );
%% Reply :: [{emqx_types:topic(), emqx_types:subopts()}]
handle_call(subscriptions, Channel = #channel{subscriptions = Subs}) ->
Reply = lists:map(
fun({_SubId, Topic, _Ack, SubOpts}) ->
{Topic, SubOpts}
end, Subs),
reply(Reply, Channel);
handle_call(kick, Channel) -> handle_call(kick, Channel) ->
NChannel = ensure_disconnected(kicked, Channel), NChannel = ensure_disconnected(kicked, Channel),
Frame = error_frame(undefined, <<"Kicked out">>), Frame = error_frame(undefined, <<"Kicked out">>),