headstock design
Overview
The headstock library is built around the central idea that XMPP is an asynchronous protocol where XML messages are identified by distinct but specific namespaces. headstock is driven by the couple "XML tag + namespace".
headstock relies therefore entirely on the capacity of the bridge XML library to perform incremental parsing. This feature suits perfectly the streamed nature of XMPP. Of course, since being able to parse XML fragments is not enough on its own, headtsock also relies on the capacity of bridge to dispatch as soon as a fragment has been parsed. This dispatching is based on a set of simple rules like the name of the top-level XML element of the fragment and its namespaces. This can also be coupled with the depth at which such element can be found during the parsing.
headstock works by registering callbacks which will handle specific XML fragment sent by the server and perform specific tasks. This is why the incremental nature is important in the parser since it allows the network client layer to simply stuff the data into the parser without caring whether or not a complete fragment has been read from the socket. The parser deals with this aspect.
headstock packages
The library tries hard to be decoupled enough so that any of its parts can be used independently.
headstock.protocol
This contains the core and extension packages. The former implements RFC 3920 and RFC 3921 which define the core of the XMPP protocol. The latter implements a set of extensions which are not compulsory to XMPP but provide the true user experience of the protocol.
The main module of the core package is stream.py which initialize the exchange with the XMPP peer.
headstock.lib.network
This package contains a set of modules that implements a simple socket clients. Those simply read and write from and to the socket. They perform little more if not handling the TLS support as well. This package currently contains:
- a threaded client which puts incoming data into a Queue instance which can be safely read from other threads
- an asynchronous client which uses the asyncore module when threads are not an option
- a .NET threaded client allowing for a better integration with IronPython
headstock.lib.auth
Modules implementing different authentication scheme such as plain, digest or Google Account Authentication.
headstock.lib
This package contains a few helper modules notably registry.py which is the base class to register callbacks to the parse instance used by the client.
headstock.api
The last package contains a set of classes that maps XMPP entities into Python objects. The reason for this package is multiple:
- Firstly callbacks registered in the headstock.protocol sub-packages will be called with a bridge.Element instance. However not every developer may find the bridge API very clean to use in their code when what they really want is accessing the data returned by the server as simply as reading an attribute from an object. Therefore headstock registers its own callbacks into the core of the library and transforms bridge Element instances into nicer objects. For example when an XMPP peer sends its version information headstock transforms the bridge instance into a headstock.api.version.VersionInfo object which can be simply used such as vinfo.name, vinfo.version, vinfo.os.
- Secondly by adding an extra but thin layer we ensure that we can more easily move the core around with breaking compatibility for developers using the library.
But of course nothing in headstock forces you to use the headstock.api package at all. It's merely a convenient package that allows you to get and set objects in a more traditionnal way rather than having to understand the bridge API. However you may of course attach your own callbacks to the core and deal with bridge Element instances instead.
The core module of this package is session.py which, as the name implies, represents the current session between two XMPP peers. This module does little more that pre-registerting a set of common callbacks using the api modules. You may of course unregister or register your own callbacks for specific XMPP events.
How does it work?
In a nutshell these are the steps taken to setup an XMPP client using headstock. By client I mean that it will initializes the exchange and listen for incoming messages.
1. Create a socket client:
from headstock.lib.network.threadedclient import ThreadedClient c = ThreadedClient('localhost', 5222) # 5222 is the default port for XMPP
2. Create a bridge parser
from bridge.parser import DispatchParser parser = DispatchParser() c.set_parser(parser) # attach the parser to the client
3. Create a stream
from headstock.protocol.core.stream import Stream stream = Stream(c) stream.initialize_all()
4. Create a session
from headstock.api.session import Session sess = Session(stream) sess.initialize_dispatchers()
5. Add your own dispatchers
def version_info_received(info): print info.name, info.version, info.os sess.version.on_received(version_info_received) def say_hello(): for jid in sess.contacts.contacts: contact = sess.contacts.contacts[jid] if contact.availability: contact.chat(u'hello', lang=u'en-GB') sess.contacts.on_update(say_hello)
6. Setup the loop that will read incoming data from the client's queue and feed the parser
def loop(): parser = c.get_parser() while keep_alive: try: data = c.incoming.get(timeout=0.01) parser.feed(data) except Queue.Empty: pass
7. Define two routines to start and stop the client
def stop(): if c.connected: stream.terminate() c.disconnect() c.join() keep_alive = False stream = c = None def start(): c.connect() c.start() stream.initiate() loop()
8. Pass the user details
# Say we have test@localhost/Home stream.set_node_name(u'localhost') stream.set_auth(u'test', u'test') # username/password stream.set_resource_name(u'Home')
9. Start the client
try:
start()
except KeyboardInterrupt:
stop()
except Exception, ex:
stop()
raise
Et VoilĂ !
IronPython support
headstock aims really hard at supporting both CPython and IronPython (hopefully Jython too someday) but it is neither thoroughly tested nor optimized. However it has been shown to function correctly on:
- IronPython? 1.1, Mono 1.2.4
- IronPython? 1.1, .NET 2.0
You will need the stdlib from CPython and IPCE extra modules from the fepy projects.
