I'm going to show how to create an external
XMPP component in
Erlang. If you're in a hurry and familiar with
ejabberd, you may want to go straight to the
source code.
This mini-project started when I decided to build
XMPP interface for the system that had already been completed. I chose external component over
ejabberd module, because I wanted to avoid
ejabberd dependencies and be able to use it with any Jabber server. As the system was written in
Erlang, and I had already written gen_mods,
Epeios seemed to be an obvious way to go, as it promotes turning standard
ejabberd modules into
XMPP components. Life turned out to be not so rosy though, after I had realized that module development in
Epeios is deprived of features that makes writing gen_mods so convenient, namely hooks and handlers.
Epeios site states that they are working on it, but for now you're totally on your own when it comes to handling all traffic routed to the component.
Epeios still handles
XEP-0114 connection protocol, but this alone wasn't enough to justify its usage.
So I decided to create a module with
XEP-0114 implementation by borrowing some code from
xmpp_component.erl in
Epeios distribution.
The
Epeios code (version 1.0.0) implies that:
- the component has start/2 and stop/1, which is what gen_mod behavior requires;
- start/2 creates a process and returns {ok, Pid}, which is a deviation from official ejabberd module development doc;
- the process created by start/2 has to be able to handle messages of form {route, JID_From, JID_To, Packet};
- to send a stanza on behalf of component, the module calls xmpp_component:send_to_server/1;
So it appears that in order to use standard
ejabberd modules in
Epeios, one should wrap them in a way that satisfies above requirements. As my goal was just ripping out some code pieces rather than reusing the whole thing, I felt that following
gen_mod wouldn't bring much value, instead I wanted a behavior that would aid component writing in a more structured way.
Hence the
gen_component behavior was created...
-module(gen_component).
-export([behaviour_info/1]).
-export([send_packet/1, component_name/0]).
% Return a list of required functions and their arity
behaviour_info(callbacks) ->
[{receive_packet, 3}, {send_packet, 1},
{component_name, 0}];
behaviour_info(_Other) ->
undefined.
% Helpers
send_packet(XmlString) ->
xep0114:send_to_server(XmlString).
component_name() ->
xep0114:component_name().
...and the code in
xmpp_component.erl was altered accordingly in order to replace
gen_mod by
gen_component.
This left me with only one dependency -
xmpp_component:start_xml_parser/1 directly loads platform-dependent
expat_erl.so driver, location of which has to be defined in
Epeios configuration file. I didn't want to tweak configuration and remember to choose suitable driver every time the component is being deployed.
Recently released
exmpp library came to rescue. The
exmpp build script automatically resolves the dependency on linked drivers that are used by XML parser. However, the interface of
exmpp XML parser is significantly different from the
ejabberd one that
Epeios is using. While I was preparing myself for a next round of tweaking, I found that
exmpp team had already taken care of this problem by providing adapters (in
src/
compat directory) that allow client code to use
ejabberd XML parser interface with
exmpp behind it. After some more coding I had a minimal component that was visible to external clients upon connection to
ejabberd and could send and receive
XMPP packets routed by
ejabberd.
References:
Your comments and questions are welcome.
Update, July 2, 2009: Revamped the code to add more
exmpp stuff; the framework now supports multiple components running on the same node (previously it was only possible to run a single component). Next will add service discovery support.
exmpp appears to be quite handy, many helpers etc. Need to spend more time with it, though - lack of docs and comprehensive examples slows me down considerably.