XEP-0030: Working with Service Discovery#
XMPP networks can be composed of many individual clients, components, and servers. Determining the JIDs for these entities and the various features they may support is the role of XEP-0030, Service Discovery, or “disco” for short.
Every XMPP entity may possess what are called nodes. A node is just a name for
some aspect of an XMPP entity. For example, if an XMPP entity provides Ad-Hoc
Commands, then it will have a node
http://jabber.org/protocol/commands which will contain information
about the commands provided. Other agents using these ad-hoc commands will
interact with the information provided by this node. Note that the node name is
just an identifier; there is no inherent meaning.
Working with service discovery is about creating and querying these nodes. According to XEP-0030, a node may contain three types of information: identities, features, and items. (Further, extensible, information types are defined in XEP-0128, but they are not yet implemented by Slixmpp.) Slixmpp provides methods to configure each of these node attributes.
Configuring Service Discovery#
The design focus for the XEP-0030 plug-in is handling info and items requests in a dynamic fashion, allowing for complex policy decisions of who may receive information and how much, or use alternate backend storage mechanisms for all of the disco data. To do this, each action that the XEP-0030 plug-in performs is handed off to what is called a “node handler,” which is just a callback function. These handlers are arranged in a hierarchy that allows for a single handler to manage an entire domain of JIDs (say for a component), while allowing other handler functions to override that global behaviour for certain JIDs, or even further limited to only certain JID and node combinations.
The Dynamic Handler Hierarchy#
global: (JID is None, node is None)
Handlers assigned at this level for an action (such as
add_feature) provide a global default behaviour when the action is performed.
jid: (JID assigned, node is None)
At this level, handlers provide a default behaviour for actions affecting any node owned by the JID in question. This level is most useful for component connections; there is effectively no difference between this and the global level when using a client connection.
node: (JID assigned, node assigned)
A handler for this level is responsible for carrying out an action for only one node, and is the most specific handler type available. These types of handlers will be most useful for “special” nodes that require special processing different than others provided by the JID, such as using access control lists, or consolidating data from other nodes.
Default Static Handlers#
The XEP-0030 plug-in provides a default set of handlers that work using in-memory disco stanzas. Each handler simply performs the appropriate lookup or storage operation using these stanzas without doing any complex operations such as checking an ACL, etc.
You may find it necessary at some point to revert a particular node or JID to
using the default, static handlers. To do so, use the method
You may also elect to only convert a given set of actions instead.
Creating a Node Handler#
Every node handler receives three arguments: the JID, the node, and a data parameter that will contain the relevant information for carrying out the handler’s action, typically a dictionary.
The JID will always have a value, defaulting to
xmpp.boundjid.bare for clients. The node value may be None or
Only handlers for the actions
get_items need to have return
values. For these actions, DiscoInfo or DiscoItems stanzas are exepected as
output. It is also acceptable for handlers for these actions to generate an
XMPPError exception when necessary.
Example Node Handler:#
Here is one of the built-in default handlers as an example:
def add_identity(self, jid, node, data): """ Add a new identity to the JID/node combination. The data parameter may provide: category -- The general category to which the agent belongs. itype -- A more specific designation with the category. name -- Optional human readable name for this identity. lang -- Optional standard xml:lang value. """ self.add_node(jid, node) self.nodes[(jid, node)]['info'].add_identity( data.get('category', ''), data.get('itype', ''), data.get('name', None), data.get('lang', None))
Adding Identities, Features, and Items#
In order to maintain some backwards compatibility, the methods
add_item do not follow the method signature pattern of
the other API methods (i.e. jid, node, then other options), but rather retain
the parameter orders from previous plug-in versions.
Adding an Identity#
Adding an identity may be done using either the older positional notation, or with keyword parameters. The example below uses the keyword arguments, but in the same order as expected using positional arguments.
xmpp['xep_0030'].add_identity(category='client', itype='bot', name='Slixmpp', node='foo', jid=xmpp.boundjid.full, lang='no')
The JID and node values determine which handler will be used to perform the
lang parameter allows for adding localized versions of identities using
Adding a Feature#
The position ordering for
add_feature() is to include the feature, then
specify the node and then the JID. The JID and node values determine which
handler will be used to perform the
xmpp['xep_0030'].add_feature(feature='jabber:x:data', node='foo', jid=xmpp.boundjid.full)
Adding an Item#
The parameters to
add_item() are potentially confusing due to the fact that
adding an item requires two JID and node combinations: the JID and node of the
item itself, and the JID and node that will own the item.
xmpp['xep_0030'].add_item(email@example.com', name='An Item!', node='owner_node', subnode='item_node', ijid=xmpp.boundjid.full)
In this case, the owning JID and node are provided with the
Performing Disco Queries#
get_items() are used to query remote JIDs
and their nodes for disco information. Since these methods are wrappers for
sending Iq stanzas, they also accept all of the parameters of the
get_items() method may also accept the boolean parameter
iterator, which when set to
True will return an iterator object using
the XEP-0059 plug-in.
info = await self['xep_0030'].get_info(firstname.lastname@example.org', node='bar', email@example.com', timeout=30) items = await self['xep_0030'].get_items(firstname.lastname@example.org', node='bar', iterator=True)
For more examples on how to use basic disco queries, check the
example in the
In some cases, it may be necessary to query the contents of a node owned by the
client itself, or one of a component’s many JIDs. The same method is used as for
normal queries, with two differences. First, the parameter
be used. Second, the return value will be a DiscoInfo or DiscoItems stanza, not
a full Iq stanza.
info = await self['xep_0030'].get_info(node='foo', local=True) items = await self['xep_0030'].get_items(email@example.com', node='bar', local=True)