Friday, July 3, 2009

Writing XMPP components in Erlang with exmpp

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.

No comments:

Post a Comment