Trucs de geek

mod_rpc : Jabber-RPC within ejabberd

Posted on septembre 12, 2007

What is mod_rpc ?

mod_rpc is an ejabberd module which will handle rpc queries … in a modular way.

It is is easily extensible, and is designed to access the mnesia database from XMPP clients.

It plugs in the access control list to allow or prevent access to the rpc modules.

Installing mod_rpc

Download the mod_rpc.erl file and copy it over your ejabberd src/ directory. make ejabberd.

Do not restart yet, we have some configuration to do !

Using mod_rpc

Let’s say you want to publish two functions, echo and mult. The code would go as follow :

rpc_test.erl :


-module(rpc_test).
-export([handle/2]).

handle(_State, {call, echo, [A]}) -> {response, [A]};
handle(_State, {call, mult, [A, B]}) -> {response, [A*B]};

Copy into your ejabberd source directory and make.

Now you need to configure access to your functions. In

ejabberd.cfg


% 2 groups : admins, and the rest.
{access, rpc_admin, [{allow, admin}]}.
{access, rpc_all, [{allow, all}]}.
%...

%... in modules configuration
%...
  {mod_register,   [{access, register}]},
  {mod_rpc, [{access, [{rpc_test, rpc_admin}] }]}, % only admins can call echo and mult
  {mod_roster,     []},
%....

Now start ejabberd. (of course you could do the hot code stuff if you want)

From now on I have rpc_test@rpc.localhost answering to my rpc queries.

Let’s test from ruby using xmpp4r. Get the SVN version for jabber-RPC support.


require 'xmpp4r'
require 'xmpp4r/rpc/helper/client'
require 'xmpp4r/rpc/helper/server'
include Jabber
jid = JID::new('cstar@localhost') #this one is admin !
cl = Client::new(jid)
cl.connect
cl.auth("PASS")
rpc= RPC::Client.new(cl, 'rpc_test@rpc.localhost')
puts rpc.call("echo", 'Test string') # outputs Test string
puts rpc.call("mult", 2,4) # outputs ... 8

If you try with a non-admin user, you’d get

Jabber::AuthenticationFailure: not-authorized

About Groovy Jabber-RPC

I have been playing with it, and it does not work directly out of the box. The groovy lib will try to see if rpc_test@rpc.localhost is in the user roster.

Patching to making it work is quite simple ; In file xmlrpc-groovy/src/main/java/groovy/net/xmlrpc/JabberRPCServerProxy.java

Just replace : request.setTo(getId(connection.getRoster(), this.to)); (line 102)

with : request.setTo(this.to);

And the following will work :


import groovy.net.xmlrpc.*
import org.jivesoftware.smack.XMPPConnection

def clientConnection = new XMPPConnection("localhost")
clientConnection.connect()
clientConnection.login("cstar", "PASS") 
def serverProxy = new JabberRPCServerProxy(clientConnection, "rpc_test@rpc.localhost")
serverProxy.echo("test")
clientConnection.disconnect()

Necessary caveats

This is my first foray in developping a module in ejabberd, I still have to check how this actually scales. I only have one process handling all queries, which is not very concurrency oriented programming :)

Thanks

The guys from ejabberd, for making software really easy to use and extend ;)

Download :

mod_rpc.erl

Feedback

I really welcome enhancements and fixes (especially regarding the concurrency stuff!)

License

Don’t sue me, don’t remove copyright/name kind of license.

[UPDATED] XMPPPool : XMPP Connection Pooling for Tomcat and Grails/Jetty

Posted on septembre 10, 2007

Release 0.3 of XMPPPool

[Update : also added support for Grails Jetty]

[Update 2 : Simplified the API a lot. It is now basically invisible …]

About

It provides XMPP connection pooling for Tomcat, configured as a resource in the $TOMCAT_HOME/conf/server.xml.

The main use case to use Groovy Jabber-RPC, with the webapp acting as a client :

  • You fetch a XMPPConnection from the pool,
  • call an RPC method,
  • get the result,
  • return the XMPPConnection to the pool.

Of course you can use the code to write XMPP “servers”/bots, but you’ll have to manage the lifecycle of the connection (i.e. launch a separate thread)

How it works

A pool is created, with connections to the XMPP server. All connections use the same XMPP account, but each have a unique resource.

Rule 10.5 of RFC 3920 (XMPP Core) means that is a stanza is sent to a JID with a specific resource, it MUST be delivered to this resource or it MUST fail. However if a stanza is sent to a jid without any resource identifier, it SHOULD be sent to at least one of the resources.

So make sure that the code answering to a query coming from an XMPPPool managed connection sends it to a specific JID.

Quick install guide for Tomcat

Download XMPPPool-0.3.0-jar-with-dependencies.jar.zip and copy it in your $TOMCAT_HOME/common/lib. Then in server.xml, configure your source as follow :


<Resource auth="Container" maxActive="10" maxIdle="5" maxWait="10000" 
  factory="com.ohmforce.xmpp.XMPPSourceFactory" type="com.ohmforce.xmpp.XMPPConnectionPool" name="xmpp/connection"
  username="username" password="pass" service="localhost" port="5222"/>

The pooling code is using commons-pool from Apache. All the usual options are available. port is optional and defaults to 5222.

Now restart your container.

Quick install guide for Grails/Jetty

First you need to patch Grails as per this report (which should be implemented soon in Grails). Don’t forget to add the jars to $GRAILS_HOME/lib!

Also unzip and add XMPPPool-0.3.0-jar-with-dependencies.jar in GRAILS_HOME/lib

Then add a jetty-env.xml file in WEB-INF/ directory (of your Grails app)


<Configure class="org.mortbay.jetty.webapp.WebAppContext">
  <New id="xmpp" class="org.mortbay.jetty.plus.naming.Resource">
    <Arg>xmpp/connection</Arg>
    <Arg>
    <New class="com.ohmforce.xmpp.XMPPSourceFactory">
     <Set name="properties">
       <New class="java.util.Properties">
         <Put name="username">username</Put>
         <Put name="password">pass</Put>
         <Put name="service">localhost</Put>
       </New>
      </Set>
   </New>
 </Arg>
   </New>
</Configure>

Start the grails app : grails -Denable.jndi=true run-app

Now inside a grails controller you can connect to a Jabber-RPC service :

Using from Grails :

Now inside a grails controller you can connect to a Jabber-RPC service :


import javax.naming.InitialContext;
import groovy.net.xmlrpc.*
class HelloController{
def hello = {
        def p =new InitialContext().lookup("java:comp/env/xmpp/connection")
        def serverProxy = new JabberRPCServerProxy(p, "cstar@localhost")
        def s = serverProxy.add(2,6)
        def e = serverProxy.echo("toto")
        flash.message = "${s} and ${e}"
        p.disconnect() // does not actually disconnect, but returns connection in pool
    }
}

The server code (slightly modified from the examples on the Groovy website. fixed typos and updated to latest Smack version) :


import groovy.net.xmlrpc.*
import org.jivesoftware.smack.XMPPConnection
def server = new JabberRPCServer()
server.echo = {return it+"toto"} 
server.add = {i,j -> return i+j} 
def serverConnection = new XMPPConnection("localhost")
serverConnection.connect() 
serverConnection.login("user", "pass") 
server.startServer(serverConnection)

Using the datasource in a servlet


try{
    InitialContext ctx = new InitialContext();
    XMPPConnection conn = (XMPPConnection) ctx.lookup("java:/comp/env/xmpp/connection");
    // Do things here
    conn.disconnect(); // does not actually disconnect, but returns connection in pool
}
catch(Exception e){
    e.printStackTrace();
}

For actually using the connection, please refer to the Smack developper guide

Building the source code

The project is built with Maven.

mvn assembly:assembly

cp target/XMPPPool-0.3.0-jar-with-dependencies.jar $TOMCAT_HOME/common/lib/

And you’re set.

Bugs ? Patches ?

  • mail : eric at ohmforce dot com
  • xmpp : cstar at cestari dot info

Known issues

  • Only one pool can be configured on a given tomcat instance (Although that should fit most use cases)

Todo

  • Better error reporting, I guess :)

License

Apache 2.0 license, the same as Smack and others libs XMPPPool is built upon. Free to use, as long as you don’t sue me or remove my name from the code …

Download