What

A gateway between Jabber clients that talk HTTP and Jabber servers that talk XMPP. This is a Jabber standard called XEP-0124

Why

Because I work behind a firewall and am restricted in what I can install on my PC. This uses HTTP to connect to a Jabber server - so it is firewall friendly - and at least one client (JWChat) is implement using AJAX and so does not require a local install.

Oh and my hosting plan gives me access to a Ruby web server but not a Java web server.

Release History

0.1

First release

0.2

How

There is really only one file here that implements XEP-0124 - xmpphttpbind.rb. You could put this anywhere you like on Ruby's load path and it will work. I use Rails so I put it in my application's lib directory.

I kept the code independent of any particular web framework, so you will need to write a short handler for HTTP requests that is responsible for forwarding the requests to the module and taking responses back from it. Here is the one I use for Rails:

require 'rexml/document'
require 'xmpphttpbind'

class RhbController < ApplicationController
  # Handles HTTP Bind requests
  def bind
    begin
      xmpp_response = XMPP::HTTPBind::parse request.raw_post
      logger.debug 'Response: ' + xmpp_response
      render :xml => xmpp_response
    rescue XMPP::HTTPBind::RHBNotFoundException => bre
      logger.debug bre.inspect
      render_text '', '404 - Not Found'
    rescue XMPP::HTTPBind::RHBForbiddenException => bre
      logger.debug bre.inspect
      render_text '', '403 - Forbidden'
    rescue Exception => e
      logger.debug e.inspect + "\n" + e.backtrace.join("\n")
      render_text '', '400 - Bad Request'
    end
  end
end
As you can see. Its pretty short.

This version correctly implements polling behaviour. If you want to force the client to poll you should set REQUESTS to 0 in xmpphttpbind.rb.

xmpphttpbind uses the xmpp4r gem which is available on rubyforge. This is the command I used to install it:

gem install xmpp4r http://rubyforge.org/frs/download.php/14264/xmpp4r-0.3.gem
Because of the way that XEP-0124 works you will have to make sure that your Ruby web server can handle more than one simultaneous connection. I use Apache+FastCGI+Rails on my production server and Apache+SCGI+Rails on my development server. I got bit by this on my development server. I had to edit config/scgi.yaml to include a :maxconns: line, specifically:

:maxconns: 8

The HTTP Jabber client I use is JWChat. You should install this on the same web site as xmpphttpbind. I put it in public/jwchat. If you're using Apache+Rails remember to edit your .htaccess file to serve jwchat up directly rather then send requests for http://yoursite/jwchat to Rails. You also need to edit the jwchat/config.js file to tell it where the client should send its HTTP requests - i.e. to the controller above. There is an example config.js file in subversion too.

Current Status

This is currently only tested with Rails+Apache+FastCGI/SCGI and JWChat. It may or may not work with any other combination - there are some things that I have not implemented yet and then there are always bound to be bugs too. However I would like to get this working across the board so let me know how it goes if you try and use it.

Internals

FastCGI works by running several process that handle HTTP requests. This is a problem if you want to hold open a persistent connection to another server and have HTTP requests route through that connection - the socket will be in one process and all of the other processes will need to make sure they use that one socket. Enter DRb (Distributed Ruby). xmpphttpbind runs a DRb server in one of the FastCGI process on a first-come, first-served basis: all requests look to see if the DRb server exists first and if so they connect to it, otherwise they create one. Race conditions between processes are avoided by grabbing a file lock for the duration of the lookup. This has another beneficial side-effect: If the specific process that created the DRb server dies, another one will be created just as soon as it is needed (and not before).