Swiften Developer's Guide


Table of Contents

1. Introduction
1. Prerequisites
2. Boost
2. Tutorial: Writing an Echo Bot
1. Connecting to a server: Clients & event loops
2. Building EchoBot
3. Reacting to events: Signals, Slots & Bind
4. Presence Management: Requests
5. Publishing version information: Responders
6. Extending Swiften with new payloads: Payloads, Parsers, and Serializers
7. Extending Swiften with new queries and responders
8. Using Swiften's convenience classes
9. Writing server components
Bibliography

Chapter 1. Introduction

1. Prerequisites

We assume that the reader is familiar with the basics of the XMPP protocol. For an overview of the XMPP protocol and its workings, see XMPP: The Definitive Guide [XMPP-TDG]

2. Boost

Swiften makes heavy use of Boost (http://boost.org) libraries, including Signal, Bind, Optional, and Smart Pointers. We introduce the basic usage of these libraries in our API throughout this manual. For detailed documentation, we refer to the Boost website.

Chapter 2. Tutorial: Writing an Echo Bot

In this chapter, we guide you through the Swiften API by building an example XMPP application: an EchoBot. This example program, taken from XMPP: The Definitive Guide [XMPP-TDG], connects to an XMPP server, logs in, and responds to all incoming messages with the exact same message. We build up our application using Swiften's basic building blocks for XMPP development, to help get a good understanding of how Swiften fundamental classes work and can be extended. In the last stage of this example, we introduce some of Swiften's convenience classes for standard XMPP tasks such as roster management.

1. Connecting to a server: Clients & event loops

As a first step, we create an application that connects to a server. The code can be seen in Example 2.1.

Example 2.1. Connecting to a server

#include <Swiften/Swiften.h>

using namespace Swift;

int main(int, char**) {
  SimpleEventLoop eventLoop;
  BoostNetworkFactories networkFactories(&eventLoop);

  Client client("echobot@wonderland.lit", "mypass", &networkFactories);
  client.setAlwaysTrustCertificates();
  client.connect();

  eventLoop.run();

  return 0;
}

The first thing this program does is construct an Event Loop. An event loop is a seemingly infinite loop that waits for external events (e.g. incoming network packets, timers being activated, input happening) to happen; when such an event comes in, it notifies interested parties of this event, and then continues listening for the next event. Since many application frameworks (such as Qt, GLib, Cocoa) use their own event loop, Swiften comes prepackaged with classes that integrate with these event loops. These classes can be found in Swiften/EventLoop. In this example, however, we don't use such a framework, so we use Swiften's own SimpleEventLoop. This class is used by simply instantiating it at the beginning of the application, and calling run() after everything is set up, which will go into an infinite loop. Apart from constructing, passing, and (if necessary) starting the event loop, you will probably have no other contact with it in the rest of the application.

Another prerequisite of Swiften's classes is an implementation of network interaction, provided through the NetworkFactories class. Swiften comes with a Boost-based network implementation, implemented in BoostNetworkFactories. As with the event loop, you probably will have no need to interact with this class apart from constructing it.

Swiften's central class for implementing XMPP applications is Client. This class handles all the interaction with the XMPP network. After constructing it with the JID and password with which we want to connect, we call connect() to instruct the client to connect to the XMPP server with the given credentials. Note that this call returns immediately; it is only when starting the event loop that network the actual connection process will start. The call to setAlwaysTrustCertificates() before connecting avoids checks for certificate validity; this is convenient for the examples in this guide (because not all servers have trusted certificates), but for production software, you should not call this.

2. Building EchoBot

To build your application, you will need to set up your build environment to use the correct include and library paths for Swiften, and link against the Swiften library. This depends on both the compiler you are using, and the flags you used to build Swiften. To get the list of compiler options, Swiften comes with a program swiften-config (located in Swiften/Config in the Swiften tree). Calling this with the --libs option will return the list of link flags, whereas calling it with the --cflags option will return the list of C(++) compiler flags.

An example of setting up a build of a Swiften application using SCons is shown in Example 2.2.

Example 2.2. SConstruct file to build Swiften application with SCons

env = Environment()
env["SWIFTEN_CONFIG"] = "/path/to/swiften-config"
env.MergeFlags(env.subst("!$SWIFTEN_CONFIG --cflags --libs"))
env.Program("EchoBot.cpp")
        

3. Reacting to events: Signals, Slots & Bind

Up to this point, our client doesn't do anything useful. In this section, we make the client react to XMPP events. The code can be seen in Example 2.3.

Example 2.3. Reacting to events: Notify whenever the client is connected to the network, and echo back incoming messages

#include <iostream>
#include <boost/bind.hpp>

#include <Swiften/Swiften.h>

using namespace Swift;
using namespace boost;

static Client* client;

void handleConnected();
void handleMessageReceived(Message::ref message);

int main(int, char**) {
  SimpleEventLoop eventLoop;
  BoostNetworkFactories networkFactories(&eventLoop);

  client = new Client("echobot@wonderland.lit", "mypass", &networkFactories);
  client->setAlwaysTrustCertificates();
  client->onConnected.connect(&handleConnected);
  client->onMessageReceived.connect(bind(&handleMessageReceived, _1));
  client->connect();

  eventLoop.run();

  delete client;
  return 0;
}

void handleConnected() {
  std::cout << "Connected" << std::endl;
}

void handleMessageReceived(Message::ref message) {
  // Echo back the incoming message
  message->setTo(message->getFrom());
  message->setFrom(JID());
  client->sendMessage(message);
}

A first thing we want to do is print out a message when the client is connected to the server. Swiften uses the signal/slot paradigm for notifying interested parties of events. A signal is an object representing a type of event. For example, Client has an onConnected signal for notifying whenever the client is connected to the network. If you are interested in a particular signal, you connect a slot to the signal. A slot represents a callback that will be called whenever a signal is emitted. Since we want to print out a message whenever we're connected to the network, we connect to the client's signal, and tell it to call handleConnected (which prints out a message):

client->onConnected.connect(&handleConnected)

Another event we're interested in is whenever a message comes in. For this purpose, Client provides a signal called onMessageReceived. The major difference with the previous onConnected signal is that this signal also can provide extra information to the callback: the actual message received. A signal can provide this extra information through one or more arguments, which will be passed to the slot's parameters. To be able to handle parameters to slots, there needs to be a more general representation of callbacks than just function pointers. This is where Boost's bind comes in: bind provides a way to construct functors (callbacks, slots, …), by combining function pointers and parameter values. For example, to connect the signal to our slot, we call:

client->onMessageReceived.connect(bind(&handleMessageReceived, _1))

This is essentially saying: when the onMessageReceived signal is emitted, call handleMessageReceived, and pass it the first parameter provided by the slot (which, in this case, is the actual message received).

The implementation of handleMessageReceived should be straightforward: put the To address in place of the From address, and send the message to the server. One thing to note is that Message::ref represents a shared pointer to a Message stanza. Shared pointers behave the same as regular pointers, except that, when the last copy of the pointer goes away, the object it points to is deleted as well. Message::ref is in fact a typedef for boost::shared_ptr<Message>. Although Swiften tends to prefer the use of the ::ref notation, you will see both forms used intermixed.

If you use a C++ compiler that supports C++0x lambda expressions (such as GCC 4.5 or Microsoft Visual Studio 2010), you can write this example in a more concise way, as illustrated in Example 2.4. However, for the remainder of this guide, we will not use this functionality.

Example 2.4. EchoBot using C++0x lambda expressions. This is currently only possible with a limited set of C++compilers.

#include <Swiften/Swiften.h>

using namespace Swift;

int main(int, char**) {
  // Set up the event loop and network classes
  SimpleEventLoop eventLoop;
  BoostNetworkFactories networkFactories(&eventLoop);

  Client client("echobot@wonderland.lit", "mypass", &networkFactories);
  client.setAlwaysTrustCertificates();
  client.onConnected.connect([&] {
    std::cout << "Connected" << std::endl;
  });
  client.onMessageReceived.connect([&] (Message::ref message) {
    message->setTo(message->getFrom());
    message->setFrom(JID());
    client.sendMessage(message);
  });
  client.connect();

  eventLoop.run();

  return 0;
}

Before moving on to the next step, we are going to rearrange our code from Example 2.3 a bit, to make it a bit cleaner. Instead of using global variables, we are going to create an EchoBot class with the current code in it. The resulting code can be found in Example 2.5.

Example 2.5. Creating an EchoBot class

#include <iostream>
#include <boost/bind.hpp>

#include <Swiften/Swiften.h>

using namespace Swift;
using namespace boost;

class EchoBot {
  public:
    EchoBot(NetworkFactories* networkFactories) {
      client = new Client("echobot@wonderland.lit", "mypass", networkFactories);
      client->setAlwaysTrustCertificates();
      client->onConnected.connect(bind(&EchoBot::handleConnected, this));
      client->onMessageReceived.connect(
          bind(&EchoBot::handleMessageReceived, this, _1));
      tracer = new ClientXMLTracer(client);
      client->connect();
    }

    ~EchoBot() {
      delete tracer;
      delete client;
    }
  
  private:
    void handleConnected() {
      std::cout << "Connected" << std::endl;
    }

    void handleMessageReceived(Message::ref message) {
      // Echo back the incoming message
      message->setTo(message->getFrom());
      message->setFrom(JID());
      client->sendMessage(message);
    }
  
  private:
    Client* client;
    ClientXMLTracer* tracer;
};

int main(int, char**) {
  SimpleEventLoop eventLoop;
  BoostNetworkFactories networkFactories(&eventLoop);

  EchoBot bot(&networkFactories);

  eventLoop.run();
  return 0;
}

The important thing to consider in this step are the changes to the signal connections. Since we are now passing member variables of a class to the signal, we need to use bind to pass in the actual object on which this member variable is called as the first parameter.

The only thing we added to this version is the ClientXMLTracer. This class will dump all incoming and outgoing XMPP messages to the console, which can be handy for debugging our bot.

4. Presence Management: Requests

The current version of our EchoBot does what it is supposed to do: it answers all incoming messages. However, although users can add the bot to their contact list, they will not see when it is online, since the bot doesn't do any presence handling yet. In this section, we explain the different steps involved in adding presence management, resulting in the code in Example 2.6.

Example 2.6. Adding presence management: Requesting the initial roster, and auto-approving incoming subscription requests.

…
class EchoBot {
  public:
    EchoBot(NetworkFactories* networkFactories) {
      …
      client->onPresenceReceived.connect(
          bind(&EchoBot::handlePresenceReceived, this, _1));
      …
    }

    …
    void handlePresenceReceived(Presence::ref presence) {
      // Automatically approve subscription requests
      if (presence->getType() == Presence::Subscribe) {
        Presence::ref response = Presence::create();
        response->setTo(presence->getFrom());
        response->setType(Presence::Subscribed);
        client->sendPresence(response);
      }
    }

    void handleConnected() {
      // Request the roster
      GetRosterRequest::ref rosterRequest = 
          GetRosterRequest::create(client->getIQRouter());
      rosterRequest->onResponse.connect(
          bind(&EchoBot::handleRosterReceived, this, _2));
      rosterRequest->send();
    }

    void handleRosterReceived(ErrorPayload::ref error) {
      if (error) {
        std::cerr << "Error receiving roster. Continuing anyway.";
      }
      // Send initial available presence
      client->sendPresence(Presence::create("Send me a message"));
    }
    …
};
…

First of all, our bot needs to listen to incoming subscription requests from users who want to add it to their roster, and automatically approve them. This is done by connecting to the onPresenceReceived signal, checking whether the incoming presence is a subscription request, and if so, respond to it with an approval (in handlePresenceReceived).

The first version of the XMPP protocol states that a client will not get any presence subscriptions until it requests the roster. To make sure we handle this, we want to make sure our bot requests the roster at login. After getting the onConnected signal, we therefore send a request to retrieve the roster. Swiften's Request classes correspond to XMPP IQ Get or Set actions. Swiften provides a set of built-in request classes for the most common tasks in Swiften/Queries/Requests, and can be easily extended to use add your own (see Section 6). Requests have an onResponse signal, which is emitted when a response comes in. This signal has 2 parameters: the actual response data (the Payload), and an optional error payload in case there was an error executing the request. To use a Request class, you construct it with the correct parameters, connect to the onResponse signal, and then send the request by calling send() on it. In this case, we're not interested in the actual payload of the response (passed as the first parameter), so we pass it a slot with only the second parameter (the error payload). When we get the roster back, we send initial presence to all our subscribers, announcing them we're online.

5. Publishing version information: Responders

Most XMPP clients have support for querying software version information of a client through [XEP-0092]. These clients send out an IQ-Get request to an entity, which responds with the requested information. We would like our bot to listen to these requests, and respond with the correct information. Swiften uses Responder classes for the purpose of responding to IQ requests, and are therefore the dual of the Request clients discussed in the previous section.

Example 2.7. Adding presence management: Requesting the initial roster, and auto-approving incoming subscription requests.

…
class EchoBot {
  public:
    EchoBot(NetworkFactories* networkFactories) {
      …
      softwareVersionResponder = new SoftwareVersionResponder(client->getIQRouter());
      softwareVersionResponder->setVersion("EchoBot", "1.0");
      softwareVersionResponder->start();
      …
    }

    ~EchoBot() {
      softwareVersionResponder->stop();
      delete softwareVersionResponder;
      …
    }
    …
  private:
    …
    SoftwareVersionResponder* softwareVersionResponder;
};
…

Using SoftwareVersionResponder is pretty straightforward, as can be seen in Example 2.7: simply construct the responder, set the correct parameters, call start(), and it will automatically respond to the incoming requests. Other Responder classes may provide signals to notify of incoming requests, or may have some other behavior. For a detailed explanation of responders, see Section 7.

6. Extending Swiften with new payloads: Payloads, Parsers, and Serializers

Swiften uses abstract datastructures for all the data that is received and sent over the XMPP network. The declaration of these datastructures can all be found in Swiften/Elements. For representing the XMPP stanzas, Swiften uses the Message, Presence, and IQ classes. Each stanza can have an arbitrary amount of child payloads, represented by the Payload class. A payload typically corresponds to a (namespaced) child XML element of a stanza; for example, the <query xmlns="jabber:iq:roster"/> element used for managing the roster is represented as a RosterPayload.

If you want to extend Swiften with your own XMPP extension, you will first need to create a payload for this extension. For example, suppose we want to reate an extension for use in our Echo bot that contains a special textual message, and add this to all our outgoing messages, we create the EchoPayload illustrated in Example 2.8. We can then append or retrieve this payload from the stanzas using Stanza::getPayload() and Stanza::addPayload(). For example, the version of our bot in Example 2.9 checks whether an incoming message contains the EchoPayload, and if not, echoes back the message, and adds an extension to the message with a descriptive text.

Example 2.8. Extending Swiften with a new payload: EchoPayload

…
class EchoPayload : public Swift::Payload {
  public:
    EchoPayload() {}

    const std::string& getMessage() const {
      return message;
    }

    void setMessage(const std::string& message) {
      this->message = message;
    }

  private:
    std::string message;
};

Example 2.9. Adding a custom extension: Using a custom element, and registering a parser (factory) and serializer for the element.

…
#include "EchoPayload.h"
#include "EchoPayloadParserFactory.h"
#include "EchoPayloadSerializer.h"

class EchoBot {
  public:
    EchoBot(NetworkFactories* networkFactories) {
      …
      client->addPayloadParserFactory(&echoPayloadParserFactory);
      client->addPayloadSerializer(&echoPayloadSerializer);
      …
    }

    ~EchoBot() {
      client->removePayloadSerializer(&echoPayloadSerializer);
      client->removePayloadParserFactory(&echoPayloadParserFactory);
      …
    }
    …
    void handleMessageReceived(Message::ref message) {
      …
      if (!message->getPayload<EchoPayload>()) {
        boost::shared_ptr<EchoPayload> echoPayload = boost::make_shared<EchoPayload>();
        echoPayload->setMessage("This is an echoed message");
        message->addPayload(echoPayload);
        client->sendMessage(message);
      }
    }
    …
  private:
    …
    EchoPayloadParserFactory echoPayloadParserFactory;
    EchoPayloadSerializer echoPayloadSerializer;
};
…

However, having the element is not enough; Swiften also needs to know how to extract this payload from the incoming stanzas, and know how to send it on outgoing stanzas. In order to do this, Swiften uses XML parsers and serializers for the payload. We therefore need to create a parser and serializer for our new payload, and register it with Client. Serializers are implemented as subclasses from PayloadSerializer, and provide the basic methods canSerialize() and serialize(). The serializer is registered using Client::addPayloadSerializer() (and unregistered using Client::removePayloadSerializer()). Parsers consist of 2 parts: a subclass of PayloadParser, which parses incoming XML in an event-driven way and builds up the payload, and a subclass of PayloadParserFactory, which is responsible for detecting whether a given parser can parse an incoming element, and creates a parser. The parser factory is registered with the client using Client::addPayloadParserFactory() (and unregistered using Client::removePayloadParserFactory()).

Although you can subclass the base classes for parsers and serializers directly, Swiften comes with utility classes that contain common functionality for parsers and serializers. For example, for our EchoBot, the parser and serializer using these utility classes is shown in Example 2.10 and Example 2.11 respectively. Registration of the parser and serializer is shown in the constructor of our EchoBot in Example 2.9.

Example 2.10. The parser and parser factory for EchoPayload

#pragma once

#include <Swiften/Swiften.h>
#include "EchoPayload.h"

class EchoPayloadParser : public Swift::GenericPayloadParser<EchoPayload> {
  public:
    EchoPayloadParser() : currentDepth(0) {}

    void handleStartElement(
        const std::string& /* element */, const std::string& /* ns */, const AttributeMap&) {
      currentDepth++;
    }

    void handleEndElement(const std::string& /* element */, const std::string& /* ns */) {
      currentDepth--;
      if (currentDepth == 0) {
        getPayloadInternal()->setMessage(currentText);
      }
    }

    void handleCharacterData(const std::string& data) {
      currentText += data;
    }

  private:
    int currentDepth;
    std::string currentText;
};

class EchoPayloadParserFactory : public Swift::GenericPayloadParserFactory<EchoPayloadParser> {
  public:
    EchoPayloadParserFactory() :
      GenericPayloadParserFactory<EchoPayloadParser>("echo", "http://swift.im/echo") {}
};

Example 2.11. The serializer for EchoPayload

#pragma once

#include <Swiften/Swiften.h>
#include "EchoPayload.h"

class EchoPayloadSerializer : public Swift::GenericPayloadSerializer<EchoPayload> {
  public:
    std::string serializePayload(boost::shared_ptr<EchoPayload> payload) const {
      XMLElement element("echo", "http://swift.im/protocol/echo");
      element.addNode(XMLTextNode::ref(new XMLTextNode(payload->getMessage())));
      return element.serialize();
    }
};

If you want to create your own parser and serializers, you can look at the built-in parsers and serializers in the Swiften library, located in Swiften/Parser/PayloadParsers and Swiften/Serializer/PayloadSerializers.

7. Extending Swiften with new queries and responders

Section 4 and Section 5 explained that Swiften provides Requests and Responder classes for querying or responding to queries of specific payloads. If you extend Swiften with your own payloads, you can use these to create your own Request or Responder subclasses. Swiften also provides convenience classes such as GenericRequest, GetResponder and SetResponder for creating your requests and responders for your custom payloads.

8. Using Swiften's convenience classes

Swiften comes with flavors of the Client class: CoreClient, which implements only the basics of connecting to the XMPP server, without any built-in responders. If you want to build a client from scratch, this class is probably your preferred Swiften interface. However, most users of Swiften will not want to bother with explicitly instantiating responders to basic functionality such as software version information etc., and will want to have the convenience of built-in responders and utility classes. In this case, you can use the Client class (a subclass of CoreClient, which implements most of the common XMPP client functionality, including roster and subscription management, VCards, Avatars, Service Discovery, Multi-User Chats, ...

9. Writing server components

Swiften also provides classes for creating server components. The Component class has a similar interface as Client, but uses the component protocol to connect with the XMPP server. Except for a few classes, the same techniques and classes for writing clients can be applied to write components. Example 2.12 illustrates how we would build a component version of our Echo bot.

Example 2.12. EchoBot as a server component

#include <iostream>
#include <boost/bind.hpp>

#include <Swiften/Swiften.h>

using namespace Swift;
using namespace boost;

class EchoComponent {
  public:
    EchoComponent(NetworkFactories* networkFactories) : jid("echo.wonderland.lit") {
      component = new Component(jid, "EchoSecret", networkFactories);
      component->onConnected.connect(bind(&EchoComponent::handleConnected, this));
      component->onMessageReceived.connect(
          bind(&EchoComponent::handleMessageReceived, this, _1));
      component->onPresenceReceived.connect(
          bind(&EchoComponent::handlePresenceReceived, this, _1));
      tracer = new ComponentXMLTracer(component);
      component->connect("wonderland.lit", 5347);
    }

    ~EchoComponent() {
      delete tracer;
      delete component;
    }
  
  private:
    void handlePresenceReceived(Presence::ref presence) {
      // Automatically approve subscription requests
      if (presence->getType() == Presence::Subscribe) {
        Presence::ref response = Presence::create();
        response->setTo(presence->getFrom());
        response->setType(Presence::Subscribed);
        component->sendPresence(response);
      }
    }

    void handleConnected() {
    }

    void handleMessageReceived(Message::ref message) {
      // Echo back the incoming message
      message->setTo(message->getFrom());
      message->setFrom(jid);
      component->sendMessage(message);
    }

  private:
    JID jid;
    Component* component;
    ComponentXMLTracer* tracer;
};

int main(int, char**) {
  SimpleEventLoop eventLoop;
  BoostNetworkFactories networkFactories(&eventLoop);

  EchoComponent bot(&networkFactories);

  eventLoop.run();
  return 0;
}

Bibliography

[XMPP-TDG] XMPP: The Definitive Guide. Peter Saint-Andre. Kevin Smith. Remko Tronçon.

[XEP-0092] Software Version. Peter Saint-Andre.