Swiften Developer's Guide
Introduction
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
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.
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
, 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.
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 .
Connecting to a server
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
and (if necessary) starting the event loop, you will probably have
no other contact with it in the rest of the application.
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.
Building EchoBot
TODO: Explain linking against the static library.
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 .
Reacting to events: Notify whenever the client is connected to the network, and echo back incoming messages
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.
Before moving on to the next step, we are going to rearrange our
code 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 .
Creating an EchoBot class
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.
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
.
Adding presence management: Requesting the initial roster, and auto-approving incoming subscription requests.
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 ). 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.
Publishing version information: Responders
Most XMPP clients have support for querying software version information
of a client through
. 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.
Adding presence management: Requesting the initial roster, and auto-approving incoming subscription requests.
Using SoftwareVersionResponder is pretty
straightforward, as can be seen in :
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
.
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.
TODO
Extending Swiften with new queries and responders
TODO
Using Swiften's convenience classes
TODO
Bibliography
XMPP-TDG
XMPP: The
Definitive Guide
Peter
Saint-Andre
Kevin
Smith
Remko
Tronçon
XEP-0092
Software Version
Peter
Saint-Andre