Saturday, July 11, 2009

XMPP gen_component update: Design and features

For the last few weeks I've been trying to put together a framework for creating XMPP components in Erlang. While the code I have now is not by any means covering all possible features the XMPP developer might need, I think it's a good starting point. I'm going to go over the interface and features of gen_component behavior that represents framework's API. Your input is very much appreciated.

gen_component implementation is backed by gen_fsm, so just like for gen_fsm, there is a certain set of callback functions that must be provided by a callback module.
XMPP (external) component could be seen as intermediary between XMPP server that routes messages, and some backend service that needs to be exposed to XMPP server's clients. The service could be anything: a database, a game, a stock ticker, Twitter feeds etc. Hence there are functions handling XMPP server conversations, such as handle_iq/4, handle_presence/4 and handle_message/4, and the functions handling interactions with backend service, such as feed_component/2 and handle_feed/2. XMPP service discovery support for the component is backed by functions disco_info/2 and disco_items/2. There are also functions that deal with life cycle of gen_component, such as start_component/6, stop_component/1 and their respective callbacks init/2 and terminate/1. Interface includs some helper functions such as send_packet/2 and print_packet/1.

As a proof of concept I built a test_component module that implements gen_component callbacks. test_component represents mocked weather alert service Jabber clients could subscribe to. The client browses available weather alerts and registers with ones he wishes to get updated with. That's how it looks like in action:

exmpp library has been used extensively in gen_component, and I found it pretty stable, although there were some gotchas that I attribute to my lack of experience. I'm planning to do a detailed code walkthrough with comments on exmpp usage.

Source code for gen_component is here.


Friday, July 3, 2009

gen_component project: service discovery with exmpp

The gen_component behavior is growing up and is making more sense to me as the XMPP component project is moving along. I can even see gen_component rivaling gen_server in the nearest future :-). Jokes aside, it will have handle_iq, handle_message, handle_presence and possibly more callbacks with helpers glueing it all together, in a fashion similar to OTP behaviors.
By the way, the component doesn't even have to be a process, depending on what exactly your component does. So far I managed to avoid maintaining conversational state, but will have to introduce it going forward. For instance, to tell the registered user from a new one, having state seems to be a must, doesn't it? Still, should I use Mnesia for it, rather than process state?

The project is now having service discovery written using exmpp. The imaginary weather alert service exposes itself through disco_info/1 and disco_items/1 (which are now part of gen_component).

That's how it looks like in Psi:


As you can see, there are two weather alerts running, each one having few nodes for specific weather alerts. Are you thinking of stock tickers, twits etc. ? Yes, everything is possible with gen_component and XMPP :-)

Check out the source code and instructions.
Next in line is in-band registration.
I'll be doing web interface with Strophe or jsJac as well once I have a chance.
I would be interested in your thoughts and ideas on gen_component.

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.