Thursday, January 28, 2010

gen_client behaviour for building XMPP clients in Erlang.

 The gen_client project aims to provide a structured way to write XMPP client code in Erlang.  The framework heavily relies on exmpp, but also borrows some ideas from my favorite Strophe javascript library. The objective of the project is to create a set of generic behaviours  and let a client developer to "fill in  the blanks", i.e. to implement callback methods pretty much in the same fashion the code based on OTP/Erlang behaviours is written.
Why not to just use exmpp, one might ask? Sure you can. However, going from basic examples 
to decently capable client code is not so easy in exmpp. Motivation behind gen_client is to make coding XMPP in Erlang as effortless as Strophe does for Javascript.
One example of what I mean by "efortless" is how exmpp controls handling of incoming stream. By default, sending and receiving stanzas happens in a single process. Clearly, this is not very useful unless your XMPP client is happy with "question-answer" flow (as in echo_client.erl example from exmpp distribution), as opposed to asynchronous flow.
Of course, exmpp has means to assign a separate process for handling incoming stream (exmpp_session:set_controlling_process/2). However, it would be nice to have this as default, which is what gen_client does.
Continuing with this,  sometimes you may need a synchronous handling. For instance, you'd have to search through the whole tree of pubsub nodes, do some calculations and send results elsewhere. While this kind of task can be coded using callbacks, it does make coding much harder to deal with compared to sequential style. With gen_client, you can choose between asynchronous (gen_client:send_packet) and synchronous (gen_client:send_sync_packet) requests.

And of course, each incoming stanza will be handled by gen_client in a spawned process, so your client can do many things at once - we are using Erlang for the reason, right?

To start with gen_client, write your module that implements gen_client behaviour. And off you go:
gen_client:start(Username,  Domain,  Host,  Port,  Password, Module, [ModuleArgs]).

Summary of features that are already there:

  • Simultaneous handling of multiple incoming stanzas;
  • Synchronous and asynchronous requests;
  • Attaching IQ/presence/message handlers at runtime (somewhat similar to Strophe's addHandler style);
  • Support for ad-hoc commands (XEP-0050) and service discovery (XEP-0030);
  • Compatibility with exmpp and hence ability to reuse its codebase.

Documentation and examples will follow time permitting. This is the work in progress, mostly experimental, so please use with caution. Usual disclaimers are in place. Please share your thoughts and ask questions, if any. This code is being used in real projects, so I appreciate any feedback from you as means of moving gen_client to a production quality.

Links:

Monday, October 12, 2009

How to make ejabberd cluster setup a bit easier

Even though there is a decent official documentation and many excellent posts on the topic of setting ejabberd cluster, it still could be confusing, as it was for me. It is possible to avoid some guessing work though and make things more "automated". Following explanation uses the directory structure of installation created by standard ejabberd binary installer:

1. Install ejabberd on a single node
2. Locate ejabberdctl.cfg in conf directory of your installation and adjust Erlang node name on the last line:

ERLANG_NODE=ejabberd@`hostname -f`  
 
3. Run ejabberd:
 your_ejabberd_dir/bin/ejabberdctl start
4. Make sure it runs:
 your_ejabberd_dir/bin/ejabberdctl status 
 By now you're done with first node.  
For the second and consequent nodes:
5. Synchronize node's Erlang cookie with one at 1st node (check out "Clustering setup" in ejabberd documentation on how it's done); 
6. repeat steps 1 to 4; 
7. Run:
 your_ejabberd_dir/bin/ejabberdctl debug 
At Erlang shell prompt (press Enter when asked "press any key"), type:
FirstNode = 'ejabberd@first', %% use the name of your first node (ejabberd@, see p.2 above)
mnesia:stop(),
mnesia:delete_schema([node()]),
mnesia:start(),
mnesia:change_config(extra_db_nodes, [FirstNode]),
mnesia:change_table_copy_type(schema, node(), disc_copies). 
 

The above script is a replacement of p.2,3 of official ejabberd clustering setup doc. It takes advantage of not having to manually figure out Mnesia location and proper syntax of the command suggested in there. 
8. End debug session by pressing Ctrl-c, Ctrl-c;
9. Continue with p.4 of official ejabberd clustering setup document. 
 
The piece of code above could probably be useful elsewhere, for example as part of ejabberd admin interface. Imagine having "Join cluster" and "Leave cluster" buttons somewhere on a Nodes page. Also, it's probably possible to save some manual work in situations where you want a bunch of ejabberd nodes to join existing cluster. In this case you could wrap something similar to above code into a single function and do rpc call on each of these nodes. All such things would obviously require a bit more work, such as checking if the running node is already part of the cluster etc.