Jabber and Lightweight Languages Do The Trick
By William Wright and Dana Moore
William and Dana Are The Authors of Jabber Developer's Handbook (Sams, 2003) And Engineers with bbn. They can be contracted at wwright@bbn.com and dana.moore@bbn.com, respective.
When it comes to instant messaging (IM), we almost always think of it as a mechanism for humans to speak with one another textually in semireal time. Perhaps because of its most common use in person-to-person (P2P) textual conversation, software developers have not considered IM as a delivery platform for person-to-systems (P2S) or distributed system-to-system (S2S) interactions. Nonetheless, it is in P2S and S2S that open instant messaging platforms such as Jabber may have the Most to off.
Consider that when developers design and deliver P2S systems like web clients and servers, we almost never think of the interactions between user and system as "conversations." This is not to say that building such systems is particularly hard, especially with an IM protocol such As Jabber Performing Four Essential Services:
.
In this article, we examine the Jabber client-side protocol. Jabber is extremely language-friendly, with mature libraries for Java, C / C , and .NET, among others. Our examples focus on Python, Perl, and Ruby.
THE JABBER Client Protocol (http://www.jabber.org/) IS Remarkable Straightforward. Every ParticIPANT IN A Jabber Identifier (JID) That Looks a Lot Like An E-mail Address:
{Username} @ {servername} / {resources}
username and servername are as you would expect in an e-mail address. The resource part of the JID is only important when a particular user has more than one session active at once and it is used to disambiguate the message destination. If no resource is specified, the Jabber server routes the message to whichever session the user has given the highest priority.The Jabber protocol is a bidirectional XML stream exchanged between the IM client and Jabber server. Over the course of a session, the client sends one whole XML document to the server and the server sends one whole XML document to the client. As a programmer using Jabber interface libraries, you do not need to be concerned about the root element of the XML stream-you only need to handle sub-elements. There Are Three Types of Subelements in the Jabber Client Protocol:
, Which Contains Regular Messages from One Jid To Another.
, Which Contains Information About The Online Status of an entity.
, Short for "Info Query," IS Used for Bookkeeping Tasks Logging Into Servers, Searching Databases, And So ON.
Elements of a jabber message
A Jabber Message Element CAN Contain Several XML Attributes That Describe The Message and How It Should Be Handled. Thase Include:
... To Its value specifies the JID of the intended recipient from Its value is the JID of the message sender id Is a string identifier for the message;.... Often a random string type Indicates how the recipient should treat this message. IF present, ITS Value IS One of: Chat (a message in a one-on-one conversation); GroupChat (a message in a multiparty; headline (a "news" item; some im clients raise a Separate Window to Notify Users of headline message); Andricates An Error Response from The Jabber Server .the Message Element Also Contains Sub-Elements That Contain The Actual Content of the Message, Including:
, Which contains the text content of the message.
The message's subject line.
.
................
Example 1 Is a Typical Message That Might Be Part of a One-on-One Chat Session. You can Tell by the
element attributes that this message is to dana @ localhost from bill @ localhost / work and is a chat type message. The to JID does not contain any resource, telling the Jabber server that if dana @ localhost has more than one session, deliver the message THE from Jid Does Include The Resource (Work, In this Case) SO Any reply is delivered to the right jabber session for bill @ localhost. this message
Element-what.
Now that we've reviewed the basic parts of a Jabber message, we'll show how to send and receive Jabber messages from scripting languages in the context of some common software development and system-administration tasks.Jabber in Ruby
Say you want to check on a computer to make sure that it's not overloaded or crashed, and have it send you a message when the load reaches a certain level. To see this data on a Linux machine, you might use the uptime command that prints Several System Status Readings in A Format Like this:
6:34 PM Up 83 Days, 7:54, 2 Uses, Load Average: 0.00, 0.02, 0.00
This line includes the current system time, how long the system has been running, how many users are logged in, and the load on the system, averaged over the last 1, 5, and 15 minutes. Since a runaway process causes load average numbers to increase, we should keep an eye on these numbers Rather than monitor them manually, we'll write a Ruby script using the jabber4r library. (http: //jabber4r.rubyforge .org /) that sends a Jabber message if something is awry .
Listing One is a Jabber client that connects to the server, then waits for messages. If the body of the message is, say, start 1.0, this client starts a new Ruby thread that repeatedly runs the uptime command and uses the number after the start As the threshold for sending notifications. if the one-minute loading average goes Above That Number, Notification Is Sent.
THE FIRST Thing this Script Needs to do Is Log Into the Jabber Server:
Session = jabber :: session.bind_digest (jid, passwd)
Session.announce_initial_presence
The first line uses the Jabber ID (jid) and password for that JID to initialize the connection and authenticate to the server. The second line lets other clients know that this client is online. That way, if someone has added this JID to their Jabber roster (other IM systems call this a "Buddy List"), they could see that the script was up and running and that, by implication, the computer was up and running. If the computer crashes, the Jabber server updates rosters to indicate that The script is offline.the next line in the script sets up a blassage is receivated. The jabber :: protocol :: message object (msg) IS passed to this block:
session.add_message_listener {| msg |
# Handle the Message
}
.. The Message object has accessors for each of the parts of a Jabber message To get the text of the body of the message, use the msg.body accessor Here, we use the Ruby split function to separate the start from the threshold number:
Value = msg.body.split [1]
. We also use the split function to parse the output of the uptime command If the load exceeds the threshold, a Jabber message is generated; see Example 2. Filling in the reply message using fields from the original message ensures that the response goes back to .
Since the script polls the uptime command every five seconds in an infinite loop, you need a way to stop it. Control-C works, but it's more elegant to have the script respond to a stop command in a Jabber message. The last few lines of the message handler look for the word "stop" in a message, and set a flag to indicate that any polling thread associated with the sender should exit. It would be easy to extend this example to run any program and send the output as a Jabber Message to Anyone Who is INTERESTED.JABBBER IN Perl
Next, we initiate a software build using the NET :: Jabber library (http: // www .jabberstudio.org / projects / netjabber /) for Perl and Jabber This approach uses the Ant Java build tool, but could be modified to handle. Make or Most Any Other Command-Line Build Tool.
As in the Ruby example, this script logs into the Jabber server and waits for messages. When it receives a message, it interprets the body of the message as the name of the Ant target to build, executes Ant, and returns the text output of .
THE ISTINE SIUR LINES in Example 3 Initialize The Jabber Client Connection, Define The Perl Functions Called WHEN THE
and
Packets are receive, connect to the jabber server, and automate to the server using the script's username, password, and resource.
ONCE THESE FORER LINES Are Complete, The Script Connects To The Server and Can send and / or receiving message. One Handy Thing to do is to send a
Packet So Other Clients Know That The Script is Online:
$ Connection-> PresenceSend (); If you have the build script in your Jabber roster, you can see at a glance whether the build system is available The only thing left is to process Jabber messages as they come in on the connection:.
While (1) {MY $ RES = $ Connection-> process ();
THE CALLING IS KILLED.
Our arguments to setcallbacks specified That WHEN A JABBER
packet is received, the presenceCB method is called. The script does not really make use of the presence information, but Example 4 is a function that shows how it's structured. The first argument to presenceCB is used by NET :: Jabber for bookkeeping and . not of interest The second argument is the presence object It contains the from field (the JID of the client sending the packet);. type field (controls how this packet should be interpreted); and show and status fields (indicates whether the client IS Temporarily Away, INTERESTED IN CHATTING, AND SO ON).
Here, We are intended in
Packets. When a Message Packet is receivated, net :: jabber calls Our message to read the function arguments and extract the message body as stock:
MY $ SID = Shift;
MY $ msg = shift;
MY $ msgtxt = $ msg-> getBody ();
Next, The Script Uses The Message Body To Build and Run An Ant Command Using Perl's System Command:
System "Ant -Logfile Antout.tmp $ Msgtxt";
MY $ BuildOutput = 'CAT ANTOUT.TMP';
In an open environment, you want to scrub the message body for shell-special characters; otherwise, someone could run any command they chose The output of the Ant process is stored in a temporary file and read into the $ buildOutput variable, which we. Use to construct the response message. Sending a jabber message from Perl Is A One-line Command, Butt One Line Can Have Several Parameters: $ connection-> messagesend (to => $ msg-> getFrom (),
Subject => "Build of $ msgtxt",
Thread => "$ msg-> getthread ()",
TYPE => $ msg-> gettype (),
Body => $ buildoutput);
As before, we use source-message attributes in the response message to ensure that the IM client knows how to handle this message. The Ant output is included as the body of the message. If we send a regular message with the content "echo" to an example Ant script (Listing Three), the response message is like that in Figure 1. If we send a similar "echo" message as a chat message, you see something like Figure 2. Because our script uses the type attribute of the INCOMING Message, The Im Client Knows To Route Regular Messages To The Main Message Window and The Chat Message To The Chat Window.
Jabber in Python
The final example creates web services using Jabber and Python via the JabberPy library (http://jabberpy.sourceforge.net/). The XML-RPC specification defines an encoding of RPC requests and responses-a method call, the arguments it consumes, And The Result It Returns and Mandates HTTP As a Transport. However, IF a Server Is Behind A Firewall, Incoming Http Requests Are Blocked. An Easy Way Around this is to replace the http transport with jabber.
First, look at a Jabber XML-RPC client in Listing Four. What's different from the ordinary Jabber conversation is that neither of the participants in the dialogue exchange the usual Jabber messages. Rather, they are going to exchange only XML-RPC requests and responses Wrapped in An (Info Query) Packet.
To Get Access To the Jabber Core Library Functions and Pack / Unpack XML-RPC Requests, You Must Import Both The Jabber and Xmlrpclib Packages:
Import XMLRPClib
Import Jabber
Next, use the argument this (in the python jabberpy library) Must Be Converted from a python tuple to a profom,:
Request = xmlrpclib.dumps ((text,), method name = method)
Next, log users into the Jabber switch using the client connect () method and log in using the auth () method. Assuming you created a user called peer-b to act as the responding XML-RPC server (Listing Five) and assigned a resource rpc to the username, you next create an info query packet addressed to peer-b @ localhost / rpc, set the query type to a remote procedure call, and set the payload to the XML-RPC request just created:
IQ = jabber.iq (to = Endpoint, Type = 'set')
IQ.SetQuery ('Jabber: IQ: RPC')
IQ.SetQueryPayload (Request)
Finally, you make the remote call and wait for the response.
Result = con.sendandWaitforResponse (IQ)
The returned response object contains the return type and an encoded payload. We test the result type to determine whether we got a good response or "fault" (error), get the payload, then parse the returned XML into a Python structure using loads ( ). The Parms Structure is an Array, But Normal Only The Zero-TH Slot is Filled.
if result.gettype () == 'result': response = str (Result.GetQueryPayload ())
PARMS, FUNC = XmlrpClib.Loads (Response)
Print Parms [0]
. Having set up the client, in Listing Five we define an XML-RPC service that is also a Jabber client The remote method (Rot13 ()) is a global method The JabberPy API provides support for all three types of message types;. In THIS CASE, WE Simply Stub Out The Listeners for Presence and Normal Message, Concentrating Only ON
Messages Because The XML-RPC Request Is Delivered That Way.
We extract the query's namespace and get the query payload from the info query passed in as the second argument;. See Example 5 (a) Uncomment the print lines if you want to see what the payload looks like as encoded XML, Example 5 (b ), and then as a python structure:
('There is a young lady of nantes', u'rot13)
We use the python structure directly later on to invoke the rot13 () Method.
Since the Jabber protocol defines several namespaces for IQ packets, we check the query type to make sure it's appropriate (jabber.NS_RPC) and start constructing a reply. We get the sender's Jabber address using iq.getFrom () and set the rest of the FIELDS in the response; see example 6 (a). Finally, We unpack the xml and use the string in an eval () to invoke the rot13 () method; see example 6 (b).
Once again, note that using the dumps () method to convert data to an XML-RPC stanza requires the data in the form of a tuple Uncomment the aforementioned print statement to see the XML stanza of data returned to the invoking client.:
Gurer jnf n lbhat ynql bs anagrf
Conclusion
We Hope We've Given you a taste of programming instant message with jabber. It's so easy to add this functionality That We Find Ourses COMING UP WITH New Ideas for It Every Day.ddj
Listing one
Require 'Jabber4R / Jabber4R'
Jid = "uptime @ localhost / uptime"
Passwd = "uptime"
$ status = {}
Session = jabber :: session.bind_digest (jid, passwd)
Session.announce_initial_presence
session.add_message_listener {| msg |
IF (msg.body.includ? "start")
Value = msg.body.split [1]
$ status [msg.from.to_s] = "running"
T = thread.new {
While $ status [msg.from.to_s] == "running"
Data = `uptime`.split [7]
IF (data.to_f> = value.to_f)
Reply = jabber :: protocol :: message.new (nil)
Reply.to = msg.from
Reply.thread = msg.thread
Reply.Type = msg.type
Reply.set_body (`uPtime`)
Reply.set_subject ("Your Uptime Request")
Session.Connection.send (reply)
end
Sleep 5
end
}
Elsif (msg.body.includ? "stop")
$ status [msg.from.to_s] = "stop"
end
}
Thread.Stop
Back to article
Listing TWO
Use strict;
Use net :: jabber 'client';
MY $ JID = 'Build @ localhost / work';
MY $ Pass = 'build';
MY $ Connection;
Sub messagecb {
MY $ SID = Shift;
MY $ msg = shift;
MY $ SRC = $ msg-> getFrom ("jid") -> getUserid ();
MY $ msgtxt = $ msg-> getBody ();
# Run Ant
MY $ buildoutput = `Ant -logfile antout.tmp $ msgtxt`;
$ buildoutput = `cat Antout.tmp`;
$ connection-> messageseend (to => $ msg-> getFrom (), Subject => "Build of $ msgtxt",
Thread => "$ msg-> getthread ()", type => $ msg-> gettype (), body => $ buildoutput);
`rm Antout.tmp`;
}
Sub presencecb {
MY $ SID = Shift;
MY $ Presence = Shift;
MY $ from = $ presence-> getFrom ();
MY $ TYPE = $ Presence-> gettype ();
MY $ show = $ presence-> getshow ();
MY $ status = $ presence-> getStatus ();
Print "$ from is now $ show / $ status / n";
}
Sub connectiontojabber {
MY $ uname;
MY $ server;
MY $ resource;
($ Uname, $ Server, $ Resource) = ($ ji = ~ /(([ ^@]* )@([ ^/]*) //(.* )/);
$ connection = new net :: jabber :: client ();
$ Connection-> setCallbacks (Message => / & messagecb, presence => / & presencecb);
MY $ status = $ connection-> connect (Hostname => $ server);
My @Result = $ connections-> Authsend (username => $ uname, password => $ pass,
Resource => $ resource);
IF ($ Result [0] NE "OK")
{
Print "Error: Authorization Failed: $ Result [0] - $ Result [1] / N";
exit (0);
}
$ connect-> presendend ();
While (1) {MY $ RES = $ Connection-> process ();
}
SUB sendmsg {
MY $ otherjid = shift;
MY $ msgtext = shift;
$ connection-> messagesend (to => $ 000jid, subject => "chat_demo_subject",
Thread => "chat_demo_thread", Type => "chat",
Body => $ msgtext);
}
ConnectTojabber ();
Back to article
Listing three
THIS ANT Script Doesn't Do Too Much.
Back to article
Listing Four
Import Jabber
Import XMLRPClib
IMPORT STRING
Import sys
Server = 'localhost'
Username = 'peer-a'
Parasword = 'peer-a'
Resource = 'rpc'
Endpoint = 'peer-b @ localhost / rpc'
Method = 'Rot13'Text = "There Was a young lady of nantes"
Request = xmlrpclib.dumps ((text,), methodname = method
Con = jabber.client (Host = Server)
TRY:
C.Connect ()
Except Ioerror, E:
Print "Unable to connection:% s"% e
sys.exit (0)
Con.auth (Username, Password, Resource)
IQ = jabber.iq (to = Endpoint, Type = 'set')
IQ.SetQuery ('Jabber: IQ: RPC')
IQ.SetQueryPayload (Request)
Result = con.sendandWaitforResponse (IQ)
if Result.getType () == 'Result':
Response = str (Result.GetQueryPayload ())
PARMS, FUNC = XmlrpClib.Loads (Response)
Print Parms [0]
Else:
Print "Error"
Con.Disconnect ()
Back to article
Listing FIVE
Import XmlrpClib, Jabber
Import Sys, RE, OS, STRING
Server = 'localhost'
UserName = 'peer-b'
Password = 'peer-b'
Resource = 'rpc'
DEF ROT13 (TEXT):
Rot = ""
For x in in (len (text)):
Byte = Ord (Text [x])
CAP = (Byte & 32)
Byte = (Byte & (~ CAP))
IF (Byte> = ORD ('a')) and (byte <= ORD ('Z'):
BYTE = (Byte - ORD ('A') 13)% 26 ORD ('A'))
BYTE = (byte | CAP)
Rot = rot chr (byte)
Return Rot
DEF IQCB (CON, IQ):
MyFromid = q.getto ()
TYPE = q.gettype ()
PayLoad = XmlrpClib.Loads (Str (IQ.GetQueryPayload ())))
if q.getQuery () == jabber.ns_rpc:
Resultiq = jabber.iq (to = q.getFrom (), Type = 'result')
Resultiq.setid (q.getid ())
Resultiq.setFrom (q.getto ())
Resultiq.setQuery (q.getQuery ())
Evalstring = payload [1] "('" payload [0] [0] ")"
# Actually Call The Requested MethodReturnparams = XmlrpClib.dumps
Resultiq.SetQueryPayload (Returnparams)
Con.send (Resultiq)
# --------- "Main" -----------------------------
Con = jabber.client (Host = Server)
TRY:
C.Connect ()
Except Ioerror, E:
Print "Couldn't Connect:% S"% e
sys.exit (0)
Else:
Print "connect"
Con.Process (1)
IF Con.Auth (username, password, resource):
Print "Authorized"
Else:
Print "Problems with Handshake:", Con.Lasterr, Con.LasterRcode
Sys.exit (1)
Con.SetiQHandler (IQCB)
TRY:
While (1):
Con.Process (300)
Except KeyboardInterrupt:
Con.Disconnect ()
Back to article