Ejabberd 14.x版本系列模块开发过程中遇到的坑
网上的资料多数都是2.x
版本的, 从Ejabberd2.x系列迁移到14.x,路上遇到很多坑坑洼洼, 这里以一个mod_cputime
模块为实例记录一下坑爹的过程.
自定义新的名称空间问题
如果你想要直接使用mod_disco:register_feature/2
来创建一个名称空间,那是不行的,为什么不行, 下面我来细讲:
你会碰到各种badxml
的错误. 从结果倒推,ejabberd应该是使用了xmpp_codec.erl
来验证交互过程中传递的XML节的有效性,无效的统统抛弃,增强安全性,这样模块就不能随意增加名称空间.
设$EJABBERD_SRC
为源码根目录.
- 在
14.07
中, 首先需要在$EJABBERD_SRC/tools/xmpp_codec.spec
增加你需要扩展的名称空间, 例如:
1 | -xml(cpu_time, #elem{name = <<"time">>, xmlns = <<"ejabberd:cputime">>, result = '$cdata', cdata = #cdata{label = '$cdata', required = true }}). -xml(cpu, #elem{name = <<"query">>, xmlns = <<"ejabberd:cputime">>, result = {cpu, '$time'}, refs = [#ref{name = cpu_time, label = '$time', min = 0, max = 1}]}). |
然后在ejabberd的源代码根目录下执行
make spec
生成新的xmpp_codec.hrl
,xmpp_codec.erl
两个文件.make && make install
ejabberdctl restart
还有一点要注意的时, 所有#xmlel
, #jid
, #iq
这些记录中对应的XML标签名称, 属性名称, 以及CDATA的数据类型必须是类似<<"iq">>
这种二进制字符串. 否则也会出现各种意想不到的错误.
Ejabberd的文档不多,也比较碎片化,为了避免掉进各种坑里面,还是多研究源码,多试错.
添加新名称空间iq处理模块
1 | -module(mod_cputime). -behaviour(gen_mod). -export([ start/2, stop/1, process_local_iq/3 ]). -include("ejabberd.hrl"). -include("jlib.hrl"). -include("logger.hrl"). -define(NS_CPUTIME, <<"ejabberd:cputime">>). start(Host, Opts) -> IQDisc = gen_mod:get_opt( iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue ), mod_disco:register_feature(Host, ?NS_CPUTIME), gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_CPUTIME, ?MODULE, process_local_iq, IQDisc), ok. stop(Host) -> mod_disco:unregister_feature(Host, ?NS_CPUTIME), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_CPUTIME), ok. process_local_iq(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) -> case Type of set -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; get -> CPUTime = element(1, erlang:statistics(runtime)) / 1000, SCPUTime = lists:flatten(io_lib:format("~.3f", [CPUTime])), Packet = #iq{type = result, sub_el = [ #xmlel{name = <<"query">>, attrs = [{<<"xmlns">>, ?NS_CPUTIME}], children = [ #xmlel{name = <<"time">>, attrs = [], children = [{xmlcdata, list_to_binary(SCPUTime)}]}]} ]}, Packet end. |
把上面的代码放到$EJABBERD_SRC/src
下面,然后make && make install
,
在配置文件/etc/ejabberd/ejabberd.yml
增加一个模块配置,如下:
1 | ## ## Modules enabled in all ejabberd virtual hosts. ## modules: mod_cputime: {} |
执行ejabberdctl restart
重启ejabberd.
验证
客户端发起一个IQ-get
请求
1 | <iq type='get' to='xmpp.myserver.info' xmlns='jabber:client'> <query xmlns='http://jabber.org/protocol/disco#info'/> </iq> |
服务端IQ-result
应答
验证模块是否能够工作
客户端发起IQ-get
请求一个CPU时间
1 | <iq type='get' to='xmpp.myserver.info' xmlns='jabber:client'> <query xmlns='ejabberd:cputime'/> </iq> |
服务端IQ-result
应答
1 | <iq from="xmpp.myserver.info" to="root@xmpp.myserver.info/1507629570141186050149354" type="result" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"> <query xmlns="ejabberd:cputime"> <time>3.840</time> </query> </iq> |
客户端代码在这里,包含三个文件, 下一篇把获取房间的用户列表模块移植到ejabberd 14.07
要用到二进制字符串的地方
这里强调一下
定义名称空间的位置
1
define(NS_CPUTIME, <<"ejabberd:cputime">>).
XML元素的属性名称和属性值
jlib:jid_to_string()
接受一个#jid
或一个三元组,User
,Server
,Resource
为字符串类型(Erlang中的字符串就是一个字符列表). 从jlib.erl
中的定义我们可以看到,内部使用了iolist_to_binary
对列表进行转换. 这里类型不要传错了
1 | jid_to_string(#jid{user = User, server = Server, resource = Resource}) -> jid_to_string({User, Server, Resource}); jid_to_string({N, S, R}) -> Node = iolist_to_binary(N), Server = iolist_to_binary(S), Resource = iolist_to_binary(R), S1 = case Node of <<"">> -> <<"">>; _ -> <<Node/binary, "@">> end, S2 = <<S1/binary, Server/binary>>, S3 = case Resource of <<"">> -> S2; _ -> <<S2/binary, "/", Resource/binary>> end, S3. |
参考资料
- Ejabberd Developers Guide
http://www.girlsnn.net/ejabberd/doc/dev.html#sec15 - 获取用户房间列表
http://blog.zlxstar.me/blog/2013/07/21/dicorvery-user-muclist - Strophe.js API文档
http://strophe.im/strophejs/doc/1.1.3/files/strophe-js.html - Ejabberd IQ 处理程序
https://www.process-one.net/en/wiki/ejabberd_IQ_handlers/