JabChapter 8
From WikiContent
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
- Send out notifications (to those who have requested them with the cvs
- 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.
</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.
</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
- Change to working directory
cd /jabber/java/
- Call the Jabber script and put to background
/usr/java/jdk1.3.1/bin/java -classpath jabberbeans.jar:. HostAlive $5 /&
- Write the running script's PID
echo $! > /tmp/HostAlive.pid
An ip-down stopper script
#!/bin/sh
- Simply kill the process using the JID written by the starter script
/bin/kill `cat /tmp/HostAlive.pid`
- 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.
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.
</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 from string import split
Server = 'gnu.pipetree.com' Username = 'cvsmsg' Password = 'secret' Resource = 'cvsmsg'
cvsuser = sys.argv[1] message =
def presenceCB(con, prs):
type = prs.getType() parts = split(prs.getFrom(), '/') 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.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.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.
We bring in a string function that we'll be needing later in the script to chop up JIDs into their component parts (username, hostname, and resource):
import jabber import sys from string import split
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.
This is what the callback for handling <presence/> elements looks like:
def presenceCB(con, prs):
type = prs.getType() parts = split(prs.getFrom(), '/') 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 = split(prs.getFrom(), '/') 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.
</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.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)
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)
</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>
