grack.com

At first glance, both rssCloud and PubSubHubbub have an interesting shortcoming that makes them difficult to use for desktop feed readers. Since both of them require HTTP callbacks to a publicly accessibly endpoint, a user is required to open up a port on their firewall.

It turns out that a subtle difference in the specifications gives PubSubHubbub a big edge in this case. While rssCloud requires your callback endpoint to live at the IP address you make your request from, PubSubHubbub allows you to subscribe any endpoint you wish by specifying a hub.callback url.

So how do we turn this into a real-time feed for desktop clients? Simple: we implement a PubSubHubbub subscriber on a publicly-available, always-on server that receives PubSubHubbub update events and wraps them in XMPP. The XMPP events are transmitted to the desktop client, where it can then process them as if it received the callbacks directly.

The server application doesn’t need to be smart. Only the “subscribe” and “publish” modes of PubSubHubbub’s protocol are required. All it needs to do is correctly route the update subscriptions to the correct XMPP account. In fact, with Google AppEngine’s new XMPP support, you can this in a few dozen lines of code, as I’ve done here:

A PubSubHubbub to XMPP gateway, hosted on Google AppEngine

Try out the gateway by entering your XMPP ID on the main page. This will give you a callback URL that you can use on Google’s main PubSubHubbub hub. Enter the URL for any PubSubHubbub-enabled field as the topic.

The code is simple, though not very robust:

@SuppressWarnings("serial")
public class Subscribe extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setStatus(204);
        XMPPService xmpp = XMPPServiceFactory.getXMPPService();
        JID jid = new JID(req.getPathInfo().substring(1));

        byte[] buffer = new byte[10 * 1024];
        req.getInputStream().read(buffer);
        xmpp.sendMessage(new MessageBuilder().withBody(
                "Got update: " + new String(buffer))
                .withRecipientJids(jid).build());
    }

    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        resp.setStatus(200);
        resp.setContentType("text/plain");

        XMPPService xmpp = XMPPServiceFactory.getXMPPService();
        JID jid = new JID(req.getPathInfo().substring(1));

        if (req.getParameter("hub.mode").equals("subscribe"))
            xmpp.sendMessage(new MessageBuilder().withBody(
                    "Subscribing to " + req.getParameter("hub.topic"))
                    .withRecipientJids(jid).build());
        else
            xmpp.sendMessage(new MessageBuilder().withBody(
                    "Unsubscribing from " + req.getParameter("hub.topic"))
                    .withRecipientJids(jid).build());

        resp.getOutputStream().print(req.getParameter("hub.challenge"));
        resp.getOutputStream().flush();
    }
}

Postscript: I really hope that PubSubHubbub gets a new name.

Read full post