JabChapter 8

From WikiContent

Jump to: navigation, search
Programming Jabber
Preface
1 - Getting started with Jabber
2 - Inside Jabber
3 - Installing the Jabber server
4 - Server Architecture and Configuration
5 - Jabber Technology Basics
6 - Jabber Namespaces
7 - User Registration and Authorization
8 - Using Messages and Presence
9 - Groupchat, : Components, and Event Models
10 - Pointers for Further Development
Appendix A
Appendix B
Colophon

Contents

Using Messages and Presence


Now that we have a decent grounding in the Jabber protocol and technology, let's put it to work for us. This chapter fits Jabber into solutions for two or three common problems and shows how the technology and features lend themselves very well to application-to-person (A2P) scenarios.

By way of introduction, we'll have a look at constructing and sending simple Jabber messages, to effect an "in-your-face" notification mechanism for a version control system. We'll also introduce a usage of the <presence/> element as an availability indicator for connecting systems. Finally we'll combine the two features (<message/> and the concept of availability) to make the notification mechanism "sensitive" to the presence of the person being notified.


CVS Notification via Jabber


CVS—the Concurrent Versions System—allows you to comfortably create and manage versions of the sources of your project. The most common use for CVS is to create and manage versions of program source code, but it can be readily used for any text files. For example, this book was written using DocBook SGML (http://www.docbook.org), and a CVS repository was used to manage different versions of the manuscript throughout the writing and editing process. CVS allowed us to maintain the original source files for the chapters, to compare those versions against edited files, and served as a place from which older versions could be retrieved. You can find out more about CVS at http://www.cvshome.org.

That's the "Versions System" part of CVS's name. The "Concurrent" part means that this facility is given an extra dimension in the form of group collaboration. With CVS, more than one person can share work on a project, and the various chunks of work carried out by each participant are coordinated—automatically, to a large extent—by CVS. Multiple changes by different people to the same file can be merged by CVS; any unresolvable conflicts (which may for example arise when more than one person changes exactly the same line of source code) are flagged and must be resolved by the participants involved.

The general idea is that you can create a project containing files and directories and have it stored centrally in a CVS repository. Depending on what sort of access is granted to this repository, other project participants can pull down a copy of the project—those files and directories—and work on it independently. In this way, each participant's work is isolated (in time and space) from the others. When the work is done, the work can be sent back to the repository and the changes will be merged into the central copy. After that, those merged changes are available to the rest of the participants.


CVS Watches and Notification


While CVS automatically handles most of the tedious merging process that comes about when more than one person works on a project, it also offers a facility that allows you to set a "watch" on one or more files in the project and be alerted when someone else starts to work on those watched files. This is useful if you wish to preempt any automatic merging process by contacting the other participant and coordinating your editing efforts with him.

There are two CVS commands involved in setting up watches and notifications. There are also a couple of CVS administrative files that determine how the notifications are carried out. Let's look at these commands and files in turn.


CVS commands


The CVS commands cvs watch and cvs notify are used, usually in combination, by project participants to set up the notification mechanism:


cvs watch on|off
Assuming we have a CVS-controlled project called proj1 and we're
currently inside a local checked-out copy of the project's files, we
first use cvs watch to tell CVS to watch a file ("turn a watch
on") that we're interested in, which is file4 in this example: :

yak:~/projects/proj1$ cvs watch on file4

This causes CVS to mark file4 as "watched," which means any time a
project participant checks out the file from the central repository,
the checked-out working copy is created with read-only attributes.
This means the participant is (initially) prevented from saving any
changes to that working copy. It is, in effect, a reminder to that
participant to use the CVS command cvs edit, specifying file4, before
commencing the edit session. Using cvs edit causes CVS to: # Remove
the read-only attribute for the file
  1. Send out notifications (to those who have requested them with the cvs
  2. watch add) that the participant has commenced editing it
cvs watch add|remove
While running cvs watch on against a file will set a marker causing
the file to be replicated with the read-only attribute when checked
out (which has the effect of "suggesting" to the participant editing
the file that he use the cvs edit command to signal that he's to
start editing), the actual determination of the notification
recipients is set up using the cvs watch add command.  : Running the
command: :

yak:~/projects/proj1$ cvs watch add file4

will arrange for the CVS notification to be sent to us when
someone else signals their intention (via cvs edit) to edit file4.


CVS administrative files


A number of administrative files used to control how CVS works are kept in the central CVS repository. Two of these files, notify and users, are used to manage the watch-based notification process:


notify
The standard notify file contains a line like this: :

ALL mail %s -s "CVS notification"

The ALL causes the formula described here to be used for any
notification requirements (an alternative to ALL is a
regular expression to match the directory name in which the edit
causing the notification is being carried out). : The rest of the line
is the formula to use to send the notification. It is a simple
invocation of the mail command, specifying a subject line (-s
"CVS notification"). The %s is a placeholder that CVS
replaces with the address of the notification's intended recipient.
The actual notification text, generated by CVS, is piped into the mail
command via STDIN.
users
The users file contains a list of notification recipient addresses: :

dj:dj.adams@pobox.com piers:pxharding@ompa.net robert:robert@shiels.com ...

This is a mapping from the user IDs (dj, piers, and
robert) of the CVS participants, local to the host where the
CVS repository is stored, to the addresses (dj.adams@pobox.com,
pxharding@ompa.net,and robert@shiels.com) that are used to
replace the %s in the formula described in the notify file.

==== The notification ==== If the contents of the notify and users files have been set up correctly, a typical notification, set up by DJ using the cvs watch on file4 and cvs watch add file4 commands, and triggered by Piers using the cvs edit file4 command, will be received in DJ's inbox looking like the one shown in Example 8-1.


A typical email CVS notification

Date: Fri, 8 Jun 2001 13:10:55 +0100 From: piers@ompa.net To: dj.adams@pobox.com Subject: CVS notification

testproject file4 --- Triggered edit watch on /usr/local/cvsroot/testproject By piers

CVS Notifications via Jabber


While email-based notifications are useful, we can add value to this process by using a more immediate (and penetrating) form of communication: Jabber. Although mail clients can be configured to check for mail automatically on a regular basis, using an IM-style client has a number of immediately obvious advantages:

  • It's likely to take up less screen real estate. * No amount of

tweaking of the mail client's autocheck frequency (which, if available, will log in, check for, and pull emails from the mail server) will match the immediacy of IM-style message push. * In extreme cases, the higher the autocheck frequency of the mail client, the higher the effect on overall system performance. * Depending on the configuration, an incoming Jabber message can be made to pop up, with greater effect. * A Jabber user is more likely to have a Jabber client running permanently than an email client. * It's more fun! The design of CVS's notification mechanism is simple and abstract enough for us to put an alternative notification system in place. If we substitute the formula in the notify configuration file with something that will call a Jabber script, we might end up with something like:


ALL python cvsmsg %s


Like the previous formula, it will be invoked by CVS to send the notification, and the %s will be substituted by the recipient's address determined from the users file. In this case, the Python script cvsmsg is called. However, now that we're sending a notification via Jabber, we need a Jabber address—a JID—instead of an email address. No problem, just edit the users file to reflect the new addresses. Example 8-2 shows what the users file might contain if we were to use JIDs instead of email addresses.


Matching users to JIDs in the notify file

dj:dj@gnu.pipetree.com piers:piers@jabber.org robert:shiels@jabber.org


As Jabber user JIDs in their most basic form (i.e., without a resource suffix) resemble email IDs, there doesn't appear to be that much difference. In any case, CVS doesn't really care, and it takes the portion following the colon separator and simply passes it to the formula in the notify file.


The cvsmsg Script


Let's now have a look at the script, called cvsmsg. It has to send a notification message, which it receives on STDIN, to a JID, which it receives as an argument passed to the script, as shown in Example 8-3.


The cvsmsg Python script

import jabber import sys

Server = 'gnu.pipetree.com' Username = 'cvsmsg' Password = 'secret' Resource = 'cvsmsg'

cvsuser = sys.argv[1] message =

for line in sys.stdin.readlines(): message = message + line

con = jabber.Client(host=Server)

try: con.connect() except IOError, e: print "Couldn't connect: %s" % e sys.exit(0)

con.auth(Username,Password,Resource) con.send(jabber.Message(cvsuser, message, subject="CVS Watch Alarm")) con.disconnect()


It's not that long but worth breaking down to examine piece by piece.

We're going to use the Jabberpy Python library for Jabber, so the first thing we do in the script is import it. We also import the sys module for reading from STDIN:


import jabber import sys


As the usage of the script will be fairly static, we can get away here with hardcoding a few parameters:


Server = 'gnu.pipetree.com' Username = 'cvsmsg' Password = 'secret' Resource = 'cvsmsg'


Specified here are the connection and authentication details for the cvsmsg script itself. If it's to send a message via Jabber, it must itself connect to Jabber. The Server variable specifies which Jabber server to connect to, and the Username, Password, and Resource variables contain the rest of the information for the script's own JID (cvsmsg@gnu.pipetree.com/cvsmsg) and password.


cvsuser = sys.argv[1] message =

for line in sys.stdin.readlines(): message = message + line


The sys.argv[1] refers to the notification recipient's JID, which will be specified by the CVS notification mechanism, as it is substituted for the %s in the notify file's formula. This is saved in the cvsuser variable. We then build up the content of our message body we're going to send via Jabber by reading what's available on STDIN. Typically this will look like what we saw in the email message body in Example 8-1:


testproject file4 --- Triggered edit watch on /usr/local/cvsroot/testproject By piers


con = jabber.Client(host=Server)


Another Jabberpy module, xmlstream, handles the connection to the Jabber server. We don't have to use that module explicitly, however; the jabber module wraps and uses it, shielding us from the details—hence the call to instantiate a new jabber.Client object into con, to lay the way for our connection to the host specified in our Server variable: gnu.pipetree.com. If no port is explicitly specified, the standard port (5222), on which the c2s service listens, is assumed.

The instantiation causes a number of parameters and variables to be initialized, and internally an xmlstream.Client object is instantiated; various parameters are passed through from the jabber.Client object (for example, for logging and debugging purposes), and an XML parser object is instantiated. This will be used to parse fragments of XML that come in over the XML stream.


try: con.connect() except IOError, e: print "Couldn't connect: %s" % e sys.exit(0)


A connection is attempted with the connect() method of the connection object in con. This is serviced by the xmlstream.Client object and an XML stream header, as described in Section 5.3, is sent to gnu.pipetree.com:5222 in an attempt to establish a client connection. An IOError exception is raised if the connection cannot be established; we trap this, after a fashion, with the try: ... except as shown.

Once connected (meaning the client has successfully exchanged XML stream headers with the server) we need to authenticate:


con.auth(Username,Password,Resource)


The auth method of the jabber.Client object provides us with a simple way of carrying out the authentication negotiation, qualified with the jabber:iq:auth namespace and described in detail in Section 7.3. Although we supply our password here in the script in plaintext (secret), the auth method will use the IQ-get (<iq type='get'...>) to retrieve a list of authentication methods supported by the server. It will try to use the most secure, "gracefully degrading" to the least, until it finds one that is supported. This is shown in Figure 8-1.


Graceful degradation in authentication


</code> Note the presence of Resource in the call. This is required for a successful client authentication regardless of the authentication method. Sending an IQ-set (<iq type='set'...>) in the jabber:iq:auth namespace without specifying a value in a <resource/> tag results in a "Not Acceptable" error 406; see Table 5-3 for a list of standard error codes and texts.

We're connected and authenticated. "The world is now our lobster," as an old friend used to say. We're not necessarily expecting to receive anything at this stage, and even if we did, we wouldn't really want to do anything with what we received anyway. So we don't bother setting up any mechanism for handling elements that might appear on the stream.


con.send(jabber.Message(cvsuser, message, subject="CVS Watch Alarm"))


The next step is to send the notification message (in message) to the user (in cvsuser). There are actually two calls here. The innermost call, jabber.Message(), creates a simple message element that looks like this:


<message to='[value in cvsuser variable]'> <subject>CVS Watch Alarm</subject> <body>[value in message variable]</body> </message>


It takes two positional (and required) parameters; any other information to be passed (such as the subject in this example) must be supplied as key=value pairs. The outermost call, con.send(), sends whatever it is given over the XML stream that the jabber.Client object con represents. In the case of the jabber.Message call, this is the string representation of the object so created (i.e., the <message/> element).

Once the notification message has been sent, the script's work is done. We can therefore disconnect from the server before exiting the script:


con.disconnect()


Calling the disconnect() method of the jabber.Client sends an unavailable presence element to the server on behalf of the user who is connected:


<presence type='unavailable'/>


This is sent regardless of whether a <presence/> element was sent during the conversation but does no harm if one wasn't.

After sending the unavailable presence information, the XML stream is closed by sending the stream's closing tag:


</stream:stream>


This signifes to the server that the client wishes to end the conversation. Finally, the socket is closed.


Dialup System Watch


These days, it's becoming increasingly common to have a server at home with a dialup connection to the Internet. Your data, your latest developments, and your mail are stored on there. This works really well when you're telecommuting and pulling those late night hacking sessions at home; you have access to all your information and can connect to the Net.

For many people, however, the reality is that it's not just at home where the work gets done. Consultants, freelancers, and people with many customers have their work cut out for them in traveling to different sites to complete jobs. One of the biggest issues in this respect, especially in Europe where dialup and pay-per-minute connections still outweigh fixed or flat-rate connections, is the accessibility of the information on the server at home, sitting behind a modem. In a lot of cases, the expense of leaving the server dialed up for the duration of the trip is far too great to be justified.

One solution is to have the server dial up and connect to the Internet at regular intervals, say, every hour or two, and remain connected for 5 or 10 minutes. If you need access to the information or need to log on to your server and run a few tests, you can hold the connection open, once you've connected to it, by running a ping, for example.

The problem here, though, is timing. Due to the inevitable synchronization problems between wristwatch and PC clock, eddies in the space-time continuum, and the fact that people simply forget to check the time, the online window of the server's dialup is often missed.

The essence of this problem is a presence thing. We need to know about the presence, the availability, of our server at home, with respect to the Internet.

Using Jabber as your IM mechanism at work, it's likely that you'll have a Jabber client of some sort on your laptop or desktop at the customer sites. Whether it's WinJab on Windows, Jarl in Command Line Interface (CLI) mode on a remote server over an SSH connection, or any other type of Jabber client and connection, the point is that the client turns out to be an ideal ready-made component for solving the dialup timing problem. Here's how it works:

  • Get the server to dial up and connect to the Internet regularly. * On

connection, start a script that sends Jabber presence to you. * On disconnection, get the script to end. If you add to your roster a JID that represents the server at home, it would be possible to subscribe to the server's presence and know when it was available—connected to the Internet—and when it wasn't.

The script we're going to write to send Jabber presence is called HostAlive.


Making Preparations for Execution


Before diving into the script, it's necessary to do a bit of preparation. We're going to be using the presence subscription concept, which was described in Chapter 5 and is covered in more detail in the next section in this chapter. We're also going to have to get the script to run, and stay running, when the dialup connection is made and have it stop when the dialup connection is ended.


Presence


Rather than get involved in the nitty-gritty of presence subcriptions right now, let's use the tools that are around us to get things set up. In order for this to work, we need to be subscribed to the presence of the script that will be invoked when the server dials up and connects to the Internet. The script will connect to the Jabber server using a JID with a username that represents the Linux server: myserver@gnu.pipetree.com. My JID in this case is dj@gnu.pipetree.com, so we just use whatever Jabber client happens to be at hand, say, Jabber Instant Messenger (JIM), to effect both sides of the subscription.


Step 1
Create JID myserver@gnu.pipetree.com
We need to create the script's JID if it doesn't already exist. We can
use the reguser script we wrote in Section 7.4 to do this: :

[dj@yak dj]$ ./reguser gnu.pipetree.com username=myserver password=secret [Attempt] (myserver) Successful registration [dj@yak dj]$

Step 2
Subscribe to myserver's presence
We start JIM with the JID dj@gnu.pipetree.com and then add
myserver@gnu.pipetree.com to the roster. This should automatically
send a presence subscription request to the JID. Adding the JID to the
roster using JIM is shown in Figure 8-2.
Step 3
Accept presence subscription as myserver
Using the JIM client, we reconnect with the myserver JID and
accept the presence subscription request from Step 2, so that
dj@gnu.pipetree.com will automatically receive
myserver@gnu.pipetree.com's availability information. Whether or
not myserver subscribes to dj's presence is irrelevant in this
case, as the script itself is not interested in the availability of
anyone at all.
Adding myserver@gnu.pipetree.com to the roster

</code> At this stage, the entry in dj@gnu.pipetree.com's roster that represents the Linux server will indicate whether the script run at dialup time is active. If we continue to use the JIM client, we will see that active status is shown by a yellow bulb and inactive by no icon at all.


Starting and stopping the script


The dialup connection is set up using the Point-to-Point Protocol daemon pppd. This uses a program such as chat to talk to the modem and get it to dial the ISP. The pppd mechanism affords us an ideal way to start and stop a script on the respective connection and disconnection of the line. When the connection has been made, the script /etc/ppp/ip-up is invoked and passed a number of connection-related parameters. Similarly /etc/ppp/ip-down is invoked when the connection is closed.

Some implementations of pppd also offer /etc/ppp/ip-up.local and /etc/ppp/ip-down.local, which should be used in place of the ip-up and ip-down scripts if they exist. These .local versions are intended to separate out system-specific connection-related activities from general connection-related activities, in a similar way to how the rc.local file allows system-specific startup activities to be defined in the /etc/rc.d/ Unix System V set of runlevel directories.

So what we want to do is start HostAlive with ip-up[.local] and stop it with ip-down[.local]. What these starter and stopper scripts might look like is shown in Example 8-4 and Example 8-5. They are simply shell scripts that share the process ID (PID) of the Jabber script via a temporary file. The starter starts the Jabber script and writes the PID of that script to a file. The stopper kills the script using the PID.


An ip-up starter script

#!/bin/sh

  1. Change to working directory

cd /jabber/java/

  1. Call the Jabber script and put to background

/usr/java/jdk1.3.1/bin/java -classpath jabberbeans.jar:. HostAlive $5 /&

  1. Write the running script's PID

echo $! > /tmp/HostAlive.pid


An ip-down stopper script

#!/bin/sh

  1. Simply kill the process using the JID written by the starter script

/bin/kill `cat /tmp/HostAlive.pid`

  1. Remove the PID file

/bin/rm /tmp/HostAlive.pid


Example 8-4 shows that we're passing through one of the parameters that pppd gives to the ip-up script: the remote IP address—by which the server is known during its temporary connection to the Internet—in the $5 variable.[1] This IP address can be passed along as part of the availability information in the <presence/> element, so that the recipient (dj) can see what IP address has been assigned to the server.


The HostAlive Script


As you might have guessed from looking at Example 8-4, we're going to write HostAlive in Java, shown in Example 8-6. We'll use the JabberBeans library; see Section P.4 in the Preface for details of where to get this library and what the requirements are.


The HostAlive script, written in Java

import org.jabber.jabberbeans.*; import org.jabber.jabberbeans.Extension.*; import java.net.InetAddress;

public class HostAlive { public static final String SERVER = "gnu.pipetree.com"; public static final String USER = "myserver"; public static final String PASSWORD = "secret"; public static final String RESOURCE = "alive";

 public static void main(String argv[]) {
   ConnectionBean cb=new ConnectionBean();
   InetAddress addr;
   try { cb.connect(addr=InetAddress.getByName(SERVER));
   }
   catch (java.net.UnknownHostException e) { 
     //from getByName()
     System.out.println("Cannot resolve " + SERVER + ":" +
     e.toString()); return;
   }
   catch (java.io.IOException e) {
     //from connect()
      System.out.println("Cannot connect to " + SERVER); return;
   }
   InfoQueryBuilder iqb=new InfoQueryBuilder(); InfoQuery iq;
   IQAuthBuilder iqAuthb=new IQAuthBuilder();
   iqb.setType("set");


   iqAuthb.setUsername(USER); iqAuthb.setPassword(PASSWORD);
   iqAuthb.setResource(RESOURCE);
   try { iqb.addExtension(iqAuthb.build());
   }
   catch (InstantiationException e) {
     //building failed ?
     System.out.println("Fatal Error on Auth object build:");
     System.out.println(e.toString()); System.exit(0);
   }


   try {
     //build the full InfoQuery packet
     iq=(InfoQuery)iqb.build();
   }
   catch (InstantiationException e) {
     //building failed ?
     System.out.println("Fatal Error on IQ object build:");
     System.out.println(e.toString()); return;
   }
   cb.send(iq);
   PresenceBuilder pb=new PresenceBuilder(); pb.setStatus(argv[0]);
   try { cb.send(pb.build());
   }
   catch (InstantiationException e) { System.out.println("Fatal Error
   on Presence object build:"); System.out.println(e.toString());
   return;
   }
   while (true) { try { Thread.sleep(9999);
     }
     catch (InterruptedException e) { System.out.println("timeout!");
     }
   }



Step by Step


We'll examine the script a chunk at a time. We start by importing the libraries (the classes) we would like to use:


import org.jabber.jabberbeans.*; import org.jabber.jabberbeans.Extension.*; import java.net.InetAddress;


The JabberBeans library is highly modular and designed so we can pick only the features that we need; in this case, however, we're just going to import the whole set of classes within the org.jabber.jabberbeans and org.jabber.jabberbeans.Extension packages, for simplicity.

We're also going to be manipulating the Jabber server's hostname, so we pull in the InetAddress class for convenience.

The script must connect to the Jabber server on gnu.pipetree.com as the myserver user. We define some constants for this:


public class HostAlive { public static final String SERVER = "gnu.pipetree.com"; public static final String USER = "myserver"; public static final String PASSWORD = "secret"; public static final String RESOURCE = "alive";


In the same way as with the Python-based CVS notification script earlier in this chapter, we also start off by building a connection to the Jabber server. As before, it's a two-stage process. The first stage is to create the connection object:


public static void main(String argv[]) {

   ConnectionBean cb=new ConnectionBean();


A ConnectionBean object represents the connection between the script and the Jabber server. All XML fragments (Jabber elements) pass through this object.

Then it's time to attempt the socket connection and the exchange of XML stream headers:


InetAddress addr;

   try { cb.connect(addr=InetAddress.getByName(SERVER));
   }
   catch (java.net.UnknownHostException e) { 
     //from getByName()
     System.out.println("Cannot resolve " + SERVER + ":" +
     e.toString()); return;
   }
   catch (java.io.IOException e) {
     //from connect()
      System.out.println("Cannot connect to " + SERVER); return;
   }


We create an Internet address object in addr from the hostname assigned to the SERVER constant. As the creation of the addr instance may throw an exception (Unknown Host), we combine the instantiation with the connection() call on the ConnectionBean object, which may also throw an exception of its own—if there is a problem connecting.

At this stage, we're connected and have successfully exchanged the XML stream headers with the Jabber server. So now we must authenticate:


InfoQueryBuilder iqb=new InfoQueryBuilder(); InfoQuery iq; IQAuthBuilder iqAuthb=new IQAuthBuilder();

   iqb.setType("set");


   iqAuthb.setUsername(USER); iqAuthb.setPassword(PASSWORD);
   iqAuthb.setResource(RESOURCE);
   try { iqb.addExtension(iqAuthb.build());
   }
   catch (InstantiationException e) {
     //building failed ?
     System.out.println("Fatal Error on Auth object build:");
     System.out.println(e.toString()); System.exit(0);
   }


   try {
     //build the full InfoQuery packet
     iq=(InfoQuery)iqb.build();
   }
   catch (InstantiationException e) {
     //building failed ?
     System.out.println("Fatal Error on IQ object build:");
     System.out.println(e.toString()); return;
   }
   cb.send(iq);


Yes, that's an awful lot. Let's take it bit by bit.

Figure 8-3 shows how the objects in this section of code interrelate and represent various parts of what we're trying to do—which is to construct an authorization packet. This takes the form of an IQ-set containing a <query/> tag qualified by the jabber:iq:auth namespace like this:[2]


<iq type='set'> <query xmlns='jabber:iq:auth'> <username>myserver</username> <password>secret</password> <resource>alive</resource> </query> </iq>


Constructing Jabber elements with the JabberBeans library uses so-called builders that allow individual element components to be created separately and then fused together into a final structure. In the code, we use two builders: an InfoQueryBuilder to construct the <iq/> envelope and an IQAuthBuilder to construct the <query/> content.

Taking the code step by step, we create or declare each of the three things, iqb, iq, and iqAuthb:


InfoQueryBuilder iqb=new InfoQueryBuilder(); InfoQuery iq; IQAuthBuilder iqAuthb=new IQAuthBuilder();


iqb
This is the builder object with which we can build
<iq/> elements.
iq
This is the <iq/> element that we're going to build.
iqAuthb
This is another builder object with which we can build IQ extensions
(<query/> tags) qualified by the
jabber:iq:auth namespace. The process of creating the
authorization packet is detailed in Figure 8-3.


Creating the authorization packet
</code> There are numbered steps in

Figure 8-3; these follow what happens in the rest of the authentication preparation:


Step 1
Set the type attribute of the IQ
We call the setType() method on the iqb object that
represents the outer IQ envelope to set the value of the type
attribute: :

iqb.setType("set");

Step 2
Set the values for the parts of the authorization section of

the element

Having constructed the iqAuthb object, which represents the
<query/> portion of the element, we fill the values
with these calls: :

iqAuthb.setUsername(USER); iqAuthb.setPassword(PASSWORD); iqAuthb.setResource(RESOURCE);

Steps 3a and 3b
Generate iqAuthb and add it to the iqb object
Once the values inside the authorization <query/> tag
are set, we can call the build() method on the object
representing that tag in iqAuthb to generate an extension
object (in other words, to assemble the tag) that can then be
attached to the iqb object using the addExtension()
method: :

try { iqb.addExtension(iqAuthb.build());

   }
   ...

Step 4
Generate iqb and assign it to the IQ object
In the same way that we generated the authorization
<query/> tag, we can generate the whole element and
assign it to iq: :

try {

     //build the full InfoQuery packet
     iq=(InfoQuery)iqb.build();
   }
   ...

Once we've constructed the authorization element, now held as the iq object, we can send it down the stream to the Jabber server with the send() method of the ConnectionBean object cb:


cb.send(iq);


Finally, once we've authenticated, we can construct the presence packet and send it using the same technique as before.[3]

We construct a new object to represent the presence packet denoting general availability—<presence/>:


PresenceBuilder pb=new PresenceBuilder();


In this case, there are no namespace-qualified extensions to add to the <presence/> element, but we do want to add the IP address that was passed into the script and available in argv[0]. We can use the setStatus() method on the presence object to set the optional <status/> to contain that IP address:


pb.setStatus(argv[0]);


After this, we can go ahead and generate the element, which will look like this:


<presence> <status>123.45.67.89</status> </presence>


After the generation with the build() call, we send it down the stream in the same way as the authorization <iq/> element:


try { cb.send(pb.build());

   }
   catch (InstantiationException e) { System.out.println("Fatal Error
   on Presence object build:"); System.out.println(e.toString());
   return;
   }


As for each of the build() calls, we must trap a possible exception that build() throws if it can't complete (for example, due to lack of information). This is the InstantiationException.

We can see the results of myserver sending such an information-laden <presence/> element to dj in Figure 8-4. As the server connects to the Internet, the Java script is started via the ip-up script, and it relays the assigned IP address, which is shown in Jarl's status bar as the availability information reaches dj's client.


myserver becoming available and relaying its IP address

</code> All that remains for the script

to do now is to hang around. While the XML stream to the Jabber server remains, and the connection is not broken, its availability will remain as it was as described by the simple <presence/> element we sent. So we simply go into a sort of hibernation. We have no hope of escaping, but it should be taken care of by the ip-down script as described earlier.


while (true) { try { Thread.sleep(9999);

     }
     catch (InterruptedException e) { System.out.println("timeout!");
     }
   }


In fact, when the ip-down script kills the script, the socket connection will be closed, but there was no clean disconnect—no <presence type='unavailable'/> was sent by the script to the Jabber server. In this case, the Jabber server will notice that the socket was closed and generate an unavailable <presence/> element on behalf of the client.


Presence-Sensitive CVS Notification


In Section 8.1 early in this chapter, we replaced the email-based CVS notification mechanism with a Jabber-based one. The script used was extremely simple—it connected to the Jabber server specified, authenticated, and sent off the notification message to the recipient JID.

What if we wanted to make the script "sensitive"? Jabber's presence concept could help us here; if we extended the mechanism to allow for the building of presence-based relationships between the notification script and the notification recipients, we can make the sending of the notification message dependent on the recipient's availability. "Presence-based relationships" refers to the presence subscription mechanism described in Section 5.4.2.3.

Here's how it would work:

  • Each potential recipient adds the JID used by the CVS notification

script to his roster and sends a subscription request to it.[4]

  • The notification script, called cvsmsg-s

("cvsmsg-sensitive"), on receipt of the presence subscription from a recipient, accepts the request and reciprocates by sending a subscription request back to that recipient.

  • On receipt of the

presence subscription from the notification script, the recipient accepts the request.

  • When the notification script starts up to send a

message, it announces its own availability with a <presence/> element, which causes the availability of the JIDs to which it has a presence subscription to be sent to it. Based on these <presence/> packets received, it can make a decision as to whether to send the notification message or not.

  • The

decision we're going to use here is an arbitrary one: if the recipient is online, we'll send the message, unless he's specified that he doesn't want to be disturbed, with the <show>dnd</show> element.

Subscription Relationships


This method will result in "balanced" subscription relationships between script and recipients. In other words, the script is subscribed to a recipient's presence, and vice versa.

Of the two presence subscription "directions," the one where the notification script subscribes to the recipient's presence (as opposed to the one where the recipient subscribes to the notification script's presence) is by far the most important. While it's not critical that the recipients know when the notification script is connected and active, it's essential that the notification script know about a recipient's availability at the time it wants to send a message.

So would it be more appropriate to create "unbalanced" subscription relationships?

An unbalanced relationship is one where one party knows about the other party's availability but not vice versa. The idea for sensitizing the notification script will work as long as the script can know about the availability of the recipients. Whether or not the opposite is true is largely irrelevant.

Nevertheless, it's worth basing the interaction on balanced, or reciprocal, presence subscriptions, primarily for simplicity's sake and also for the fact that most Jabber clients (and most users of these clients) tend to cope well and consistently with balanced subscriptions, whereby the representation and interpretation of unbalanced relationships is dealt with and understood in different manners. Some clients use a lurker group to classify one-way presence subscriptions from other JIDs (a "lurker" being one that can see you while you can't see it).

Far from being nebulous concepts, balanced and unbalanced subscription relationships are characterized technically by values of a certain attribute specified in each item—each JID—in a roster: the subscription attribute of the <item/> tags within the roster. As we progress through the extensions to the CVS notification script,we'll be examining these values at various stages in this recipe description in Section 8.3.3.


{{Sidebar|AnthropomorphismIt's worth pointing out at this stage that adding a JID that's used by a script to connect to Jabber is slightly symbolic of the extension of the instant messaging world into the wider arena of A2P messaging. Adding a service JID to your roster and sharing presence information with that service immediately widens the scope of what's possible with a humble instant messaging client, and blurs the boundaries between people and applications.

</code>


The cvsmsg-s Script


The script, as it stands in Section 8.1.3, is what we want to extend and make sensitive to presence. Example 8-7 looks at the extended script, cvsmsg-s, and then walks through the additions.


The cvsmsg-s script

import jabber import sys

Server = 'gnu.pipetree.com' Username = 'cvsmsg' Password = 'secret' Resource = 'cvsmsg'

cvsuser = sys.argv[1] message =

def presenceCB(con, prs):

   type = prs.getType() parts = prs.getFrom().split('/') who =
   parts[0]
   if type == None: type = 'available'
   # Subscription request: - Accept their subscription - Send request
   # for subscription to their presence
   if type == 'subscribe': print "subscribe request from %s" % (who)
   con.send(jabber.Presence(to=who, type='subscribed'))
   con.send(jabber.Presence(to=who, type='subscribe'))
   # Unsubscription request: - Accept their unsubscription - Send
   # request for unsubscription to their presence
   elif type == 'unsubscribe': print "unsubscribe request from %s" %
   (who) con.send(jabber.Presence(to=who, type='unsubscribed'))
   con.send(jabber.Presence(to=who, type='unsubscribe'))
   elif type == 'subscribed': print "we are now subscribed to %s" %
   (who)
   elif type == 'unsubscribed': print "we are now unsubscribed to %s" %
   (who)
   elif type == 'available': print "%s is available (%s/%s)" % (who,
   prs.getShow(), prs.getStatus()) if prs.getShow() != 'dnd' and who ==
   cvsuser: con.send(jabber.Message(cvsuser, message, subject="CVS
   Watch Alarm"))
   elif type == 'unavailable': print "%s is unavailable" % (who)

for line in sys.stdin: message += line

con = jabber.Client(host=Server)

try: con.connect() except IOError, e: print "Couldn't connect: %s" % e sys.exit(0)

con.auth(Username,Password,Resource)

con.setPresenceHandler(presenceCB) con.requestRoster() con.sendInitPresence()

for i in range(5): con.process(1)

con.disconnect()



Taking the cvsmsg-s Script Step by Step


Now it's time to examine the script step by step. We'll concentrate mostly on the additions to the original cvsmsg script.


import jabber import sys

Server = 'gnu.pipetree.com' Username = 'cvsmsg' Password = 'secret' Resource = 'cvsmsg'

cvsuser = sys.argv[1] message =



Presence callback


The next addition to the script is a callback to handle <presence/> elements. The callback in this script takes the form of a subroutine called presenceCB() ("presence callback"). Callbacks, in relation to programming with Jabber, are explained in Section 8.3.4.

We use a string method, split, to chop up JIDs into their component parts (username, hostname, and resource).

This is what the callback for handling <presence/> elements looks like:


def presenceCB(con, prs):

   type = prs.getType() parts = prs.getFrom().split('/') who =
   parts[0]
   if type == None: type = 'available'
   # Subscription request: - Accept their subscription - Send request
   # for subscription to their presence
   if type == 'subscribe': print "subscribe request from %s" % (who)
   con.send(jabber.Presence(to=who, type='subscribed'))
   con.send(jabber.Presence(to=who, type='subscribe'))
   # Unsubscription request: - Accept their unsubscription - Send
   # request for unsubscription to their presence
   elif type == 'unsubscribe': print "unsubscribe request from %s" %
   (who) con.send(jabber.Presence(to=who, type='unsubscribed'))
   con.send(jabber.Presence(to=who, type='unsubscribe'))
   elif type == 'subscribed': print "we are now subscribed to %s" %
   (who)
   elif type == 'unsubscribed': print "we are now unsubscribed to %s" %
   (who)
   elif type == 'available': print "%s is available (%s/%s)" % (who,
   prs.getShow(), prs.getStatus()) if prs.getShow() != 'dnd' and who ==
   cvsuser: con.send(jabber.Message(cvsuser, message, subject="CVS
   Watch Alarm"))
   elif type == 'unavailable': print "%s is unavailable" % (who)


Phew! Let's take it a bit at a time. The first thing to note is what's specified in the subroutine declaration:


def presenceCB(con, prs):


As a handler, the subroutine presenceCB() will be passed the connection object in con, and the presence node in prs. con is the same connection object that is created later in the script (con = jabber.Client(host=Server)) and is passed in for convenience, as it's quite likely we're going to want to use it, say, to send something back over the stream.

The presence node in prs is an object representation of the XML fragment that came in over the stream and was parsed into its component parts. The object is an instance of the jabber.Presence class, which is simply a specialization of the more generic jabber.Protocol class, as are the other classes that represent the other two Jabber protocol elements that are to be expected: jabber.Message and jabber.Iq. The jabber.Protocol class represents protocol elements in general.

As such, there are a number of <presence/> element-specific methods we can call on the prs object, such as getShow() and getStatus() (which return the values of the <show/> and <status/> tags—children of the <presence/> element—respectively) and general element methods such as getID, which returns the value of any id attribute assigned to the element, and setTo(), which can be used to address the element—to set the value of the to attribute.

The first thing the handler does is to call a few of these element methods to determine the type of <presence/> element (presence types are described in Section 5.4.2), and who it's coming from:


type = prs.getType() parts = prs.getFrom().split('/') who = parts[0]


When the notification script is called, the JID found in the CVS users file is substituted for the %s in the formula contained in the CVS notify file. So if the user dj were to be notified, the JID passed to the script would be dj@gnu.pipetree.com.

The way JIDs are passed around independently of the context of a Jabber session is usually in the simpler form—username@hostname, that is, without the resource suffix—username@hostname/resource. As described in Chapter 5, the resource is primarily used to distinguish individual sessions belonging to one Jabber user.

But when the Jabber library—and subsequently a handler subroutine in the script—receives an element, it contains a from attribute whose value has been stamped by the Jabber server as it passes through. The value represents the session, the connection, from which the <presence/> element was sent and, as such, includes a resource suffix. So in order to properly match up the source JID for any incoming <presence/> element with the JID specified when the script was invoked (contained in the cvsuser variable), we need to strip off this resource suffix. The remaining username@hostname part is captured in the who variable.

There's one more step to determine the presence type. The type attribute is optional; its absence signifies the default presence type, which is available. So we effect this default substitution here to make the subsequent code clearer:


if type == None: type = 'available'


At this stage, we want to take different actions depending on what sort of presence information has arrived. Recalling the sequence of events in the reciprocal presence subscription exchange described earlier in this chapter, one of the activities is for a potential notification recipient to subscribe to the presence of the script's JID.

This subscription request is carried in a <presence/> element, with a type of subscribe. Example 8-8 shows what a typical subscription request would look like.


A presence subscription request from dj@gnu.pipetree.com

<presence type='subscribe' to='cvsmsg@gnu.pipetree.com' from='dj@gnu.pipetree.com/work'/>


At this stage, dj@gnu.pipetree.com has just sent a request to subscribe to the script's presence. The subscription relationship between the two parties is nondescript, and this is reflected in the details of the item in dj's roster that relates to the script's JID:


<item jid='cvsmsg@gnu.pipetree.com' subscription='none' ask='subscribe'/>


The relationship itself is reflected in the subscription attribute, and the current state of the relationship is reflected in the

ask attribute.

If a subscription request is received, we want the script to respond by accepting the subscription request. Once the request has been accepted, a presence subscription request is made in return.

This incoming subscription request is handled here:


# Subscription request:

   # - Accept their subscription - Send request for subscription to
   # their presence
   if type == 'subscribe': print "subscribe request from %s" % (who)
   con.send(jabber.Presence(to=who, type='subscribed'))
   con.send(jabber.Presence(to=who, type='subscribe'))


Each call to the jabber.Presence class constructor creates a node representing a <presence/> element. The two parameters passed in the call are fairly self-explanatory: we specify to whom the <presence/> element should be sent, and the type.

If the presence subscription request came in from the JID dj@gnu.pipetree.com, then the XML represented by the node created in the first call here (specifying a presence type of subscribed) would look something like that in Example 8-9.


Acceptance of a presence subscription request from dj@gnu.pipetree.com

<presence type='subscribed' to='dj@gnu.pipetree.com'/>


{{Sidebar|Addressing <presence/> ElementsIt's worth pointing out here that there's a subtle difference between sending <presence/> elements in a presence subscription conversation and sending general "availability" <presence/> elements.

In the first case, we use a to attribute, because our conversation is one-to-one. In the second, we don't; our unaddressed availability information is caught by the server and in turn sent on to those entities that are subscribed to your presence.

Although you can send <presence/> elements that convey availability information directly to a JID, it's not normal. However, explicitly addressing the elements in a subscription scenario is essential.

There's another situation in which such "directed" (explicitly addressed) <presence/> elements are used—to partake of the services of the availability tracker. This is described in the Section 5.4.2.4.

</code> Once constructed, each of the jabber.Presence nodes is sent back along the stream with the con.send() calls.

Now that the script has accepted djs subscription request, djs roster item for the script reflects the new relationship:


<item jid='cvsmsg@gnu.pipetree.com' subscription='to'/>


subscription='to' denotes that the subscription relationship is currently one way—dj has a subscription to the script. There's no ask attribute as there's no current request going from dj to the script.

While dj's roster item for the script shows a subscription value of to, the script's roster item for dj shows a subscription value of from:


<item jid='dj@gnu.pipetree.com' subscription='from' ask='subscribe'/>


which shows that the script has a subscription from'dj.

Furthermore, remember that the script not only accepts dj's subscription request, it sends a reciprocal one of its own. (Hence the ask="subscribe' status in the item.) When dj accepts this request, the roster item changes yet again to reflect the balanced relationship:


<item jid='cvsmsg@gnu.pipetree.com' subscription='both'/>


We want the script to handle requests to unsubscribe from its presence in the same way:


# Unsubscription request:

   # - Accept their unsubscription - Send request for unsubscription to
   # their presence
   elif type == 'unsubscribe': print "unsubscribe request from %s" %
   (who) con.send(jabber.Presence(to=who, type='unsubscribed'))
   con.send(jabber.Presence(to=who, type='unsubscribe'))


The only difference between this section and the previous one is that it deals with requests to unsubscribe as opposed to subscribe to presence. Otherwise it works in exactly the same way. A sequence of <presence/> elements used in an "unsubscription conversation" between dj and the script, and the changes to the roster <item/> tags on each side, is shown in Figure 8-5.


<presence/> elements and roster <item/>s in an unsubscription conversation


</code> While we must take action on presence types subscribe and unsubscribe, we don't really need to do anything for their acknowledgment counterparts: subscribed and unsubscribed ("I have accepted your request, and you are now subscribed/unsubscribed to my presence").

Nevertheless, just for illustration purposes, we'll include a couple of conditions to show what's going on when the script runs:


elif type == 'subscribed': print "we are now subscribed to %s" % (who)

   elif type == 'unsubscribed': print "we are now unsubscribed to %s" %
   (who)

Apart from the types of <presence/> element covering the presence subscription process, we should also expect the basic availability elements:


<presence>...</presence>


and


<presence type='unavailable'/>


It's an available <presence/> element that the functionality of the script hinges on:


elif type == 'available': print "%s is available (%s/%s)" % (who, prs.getShow(), prs.getStatus()) if prs.getShow() != 'dnd' and who == cvsuser: con.send(jabber.Message(cvsuser, message, subject="CVS Watch Alarm"))


This presenceCB() subroutine is set up to handle <presence/> elements. In a typical execution scenario, where the script is subscribed to the presence of many potential CVS notification recipients, the subroutine is going to be called to handle the availability information of all recipients who happen to be connected to Jabber at the moment of notification. We're interested in the availability information of only one particular recipient (who == cvsuser), and we want to check on the contents of the <show/> tag.

If we get a match, we can send the notification message by creating a jabber.Message node that will look like this:


<message to='dj@gnu.pipetree.com'> <subject>CVS Watch Alarm</subject> <body> testproject file4 --- Triggered edit watch on /usr/local/cvsroot/testproject By piers </body> </message>


As in the cvsmsg script, once created, the node can be sent with the con.send() method call.

Like the conditions for the presence subscription and unsubscription acknowledgments, we're including a final condition to deal with the case where a recipient disconnects from the Jabber server during the execution of the script: an unavailable<presence/> element will be sent:


elif type == 'unavailable': print "%s is unavailable" % (who)


We're simply logging such an event for illustration purposes.


Connection and authentication


Most of the main part of the script is the same as the nonsensitive version from Section 8.1: reading in the notification message, preparing a connection to the Jabber server, and trying to connect:


for line in sys.stdin: message += line

con = jabber.Client(host=Server)

try: con.connect() except IOError, e: print "Couldn't connect: %s" % e sys.exit(0)

con.auth(Username,Password,Resource)



Registration of <presence/> handler


While we've defined the presenceCB() subroutine to handle <presence/> packets, we haven't actually told the Jabber library about it. The call to the setPresenceHandler() method of the connection object does this for us, performing the "Register handler" step shown in Figure 8-6. The steps shown in Figure 8-6 are described in Section 8.3.4.


con.setPresenceHandler(presenceCB)


Handlers and the relationship between the Jabber library and your script


</code>

Request for roster


It's easy to guess what the next method call does:


con.requestRoster()


It makes a request for the roster by sending an IQ-get with a query qualified by the jabber:iq:roster namespace:


<iq type='get' id='3'> <query xmlns="jabber:iq:roster'/> </iq>


to which the server responds with an IQ-result:


<iq type='result' id='3'> <query xmlns='jabber:iq:roster'> <item jid='dj@gnu.pipetree.com' subscription='both'/> <item jid='piers@jabber.org' subscription='both'/> <item jid='shiels@jabber.org' subscription='both'/> ... </query> </iq>


However, as there are no explicit references to the roster anywhere in the script, it's not as easy to guess why we request the roster in the first place. We know that the client-side copy is merely a "slave" copy, and, even more relevant here, we know that subscription information in the roster <item/> tags is managed by the server—we as a client don't need to (in fact, shouldn't) do anything to maintain the subscription and ask attributes and keep them up to date.

So why do we request it? Basically, it's because there's a fundamental difference between <presence/> elements used to convey availability information and <presence/> elements used to convey presence subscription information. If John sends Jim availability information in a <presence/> element, whether directly (with a to attribute) or indirectly (through the distribution of that element by the server to Jim as a subscriber to John's presence), and Jim's offline on holiday, it doesn't make sense to store and forward the message to him when he next connects:

Jabber server: "Here's some availability information for John, dated 9 days ago." Jim: "Who cares?"
The <presence/> elements conveying

availability information are not stored and forwarded if they can't be delivered because the intended recipient is offline. What would be the point?

However, <presence/> elements that convey subscription information are a different kettle of fish. While it's not important that a user is sent out of date availability information when he next connects to his Jabber client, any subscription (or unsubscription) requests or confirmations that were sent to him are important. So they need to be stored and forwarded.

As we've already seen, the presence subscription mechanism and rosters are inextricably linked. And if we look briefly under the covers, we see how this is so. When a presence subscription request is sent to a user, it runs the gauntlet of modules in the JSM (see Section 4.4.4 for details on what these modules are). The roster-handling module mod_roster grabs this request, and, just in case the recipient turns out not to be connected, stores it.

And here's how intertwined the presence subscription mechanism and rosters really are: the request is stored as a cluster of attribute details within an <item/> tag in the roster belonging to the recipient of the presence subscription request. It looks like this:


<item jid='user@hostname' subscription='none' subscribe= hidden=/>


On receipt of a presence subscription request, the mod_roster module will create the roster item if it doesn't exist already and then assign the attributes related to presence subscription—subscription="none' and subscribe="'—to it. There's no ask attribute, as this is assigned only to the item on the roster belonging to the sender, not the one belonging to the receiver, of the subscription request.

The subscribe attribute is used to store the reason for the request, that, if specified, is carried in the <status/> tag of the <presence/> element that conveys the request. If no reason is given, the value for the attribute is empty, as shown here. Otherwise, it will contain what was stored in the <status/> tag. Example 8-10 shows a presence subscription request that carries a reason.


A presence subscription request with a reason

<presence to='dj@gnu.pipetree.com'> <status>I'd like to keep my eye on you!</status> </presence>


The hidden attribute here:


<item jid='user@hostname' subscription='none' subscribe= hidden=/>


is used internally by mod_roster to mark the item as nondisplayable; it effectively is a pseudo <item/> that, when brought to life, actually turns out to be a <presence/> element. So when a request for the roster is made, mod_roster makes sure that it doesn't send these "hidden" items. The hidden attribute always has an empty value, as shown here.

After storing the subscription request, mod_roster will actually send the original <presence/> element that conveyed that request to the recipient—that is, if the recipient is online and if the recipient has already made a request for his roster. As sending an availability presence packet:


<presence/>


causes the mod_offline module to forward any messages stored offline in that user's absence, so requesting the roster:


<iq type='get'><query xmlns='jabber:iq:roster'/></iq>


causes the mod_roster module to forward any subscription requests stored offline in that user's absence.


Sending of availability information


OK. We've connected, authenticated, defined, and registered the callback to handle <presence/> elements, and requested the roster, so mod_roster will send us any presence subscription (or unsubscription) requests. Now we need to make an availability announcement in the form of a simple <presence/> element:


<presence/>


We can do this by calling the sendInitPresence() method on the connection object:


con.sendInitPresence()


This availability information will be distributed to all the entities that are subscribed to the script's presence and are online at that moment. It will also signify to the Jabber server that we are properly online—in which case it can forward to us any messages that had been stored up in our absence.

We're not really expecting any <message/> elements; indeed, we haven't set up any subroutine to handle them, so they'd just be thrown away by the library anyway. The real reason for sending presence is so that the server will actively go and probe those in a presence subscription relationship with the script and report back on those who are available (who have themselves sent their presence during their current session). This causes <presence/> elements to arrive on the stream and make their way to the presenceCB() handler.



Waiting for packets


Once everything is set up, and the script has announced its presence, it really just needs to sit back and listen to the <presence/> elements that come in. If one of these is from the intended notification recipient, and the availability state is right (i.e., not in dnd mode), we know that the circumstances are appropriate for sending the notification.

But the elements being sent over the stream from the server don't spontaneously get received, parsed, and dispatched; we can control when that happens from the script. This is the nub of the symbiosis between the element events and the procedural routines, and it's name is process().

Calling process() will check on the stream to see if any XML fragments have arrived and are waiting to be picked up. If there are any, Steps 3 through 5, shown in Figure 8-6 and described in Section 8.3.4, are executed. The numeric value specified in the call to process() is the number of seconds to wait for incoming fragments if none is currently waiting to be picked up. Specifying no value (or 0) means that the method won't hang around if nothing has arrived. Specifying a value of 30 means that it will wait up to half a minute. We really want something in between, and it turns out that waiting for up to a second for fragments in a finite loop like this:


for i in range(5): con.process(1)


will allow for a slightly stuttered arrival of the <presence/> elements that are sent to the script as a result of the server-initiated probes.


Finishing up


We're just about done. The <presence/> elements that arrive and find their way to the callback are examined, and the CVS notification message is sent off if appropriate. Once the process() calls have finished, and, implicitly, the (potentially) multiple calls to presenceCB, there's nothing left to do. So we simply disconnect from the Jabber server, as before:


con.disconnect()



Jabber Programming and Callbacks


When programming all but the simplest Jabber scripts, you're going to be using callbacks, as we've seen in this recipe. Callbacks are also known as handlers.

Rather than purely procedural programming ("do this, then do that, then do the other"), we need a different model to cope with the event-based nature of Jabber or, more precisely, the event-based nature of how we converse using the Jabber protocol over an XML stream. Although we control what we send over the XML stream connection that we've established with the Jabber server, we can't control what we receive, and more importantly, we can't control when we receive it. We need an event-based programming model to be able to handle the protocol elements as they arrive.

The libraries available for programming with Jabber offer callback mechanisms. With these callback mechanisms, we can register subroutines with the part of the library that's handling the reception of XML document stream fragments. Then, whenever an element appears on the incoming stream (a fragment in the stream document that the Jabber server is sending to us), the library can pass it to the appropriate subroutine in the script for us to act upon—to be "handled." This passing of elements to be handled by callbacks is referred to as dispatching.

Figure 8-6 shows the relationship between the library and script, and the sequence of events surrounding registering a handler and having it called. Here are the steps shown:


Step 1
Register handler
The script uses a library function to register a subroutine—in this
case, it's presenceCB()—as a handler with the library. In the
registration, the subroutine is assigned as a handler for
<presence/> elements.
Step 2
<presence/> element arrives
An XML fragment arrives on the stream, sent by the Jabber server.
Step 3
Parse, and create node
The fragment is parsed into its component parts by an XML parser, and
a node is created. A "node" is simply a term used to describe a
succinct XML fragment—containing attributes, data, and child tags—that
is usually in the form of an object that is programmatically
accessible. The node creation step is theoretically optional; we could
have the library pass on the fragment in a simple string
representation form, but that would put the onus on the script to
parse the string before being able to manipulate the fragment that the
string represented.
Step 4
Determine handler
Once parsed, the library looks at what sort of element, or node, the
fragment is and determines what (if any) handler has been registered.
In this case, it's a <presence/> element, and it finds
that the subroutine presenceCB() has been registered as a
handler for <presence/> elements.
Step 5
Call handler
The library calls the handler presenceCB() in the script,
passing in the node. It may pass in other information too (for
example, the Jabberpy library also passes in the stream
connection object, as we saw earlier, and the Perl library
Net::Jabber also passes in a session ID relating to the
stream).[5]</content></chapter>
Personal tools