Jan 29, 2008 @ 10:04 am

In the first part, we talked less about XMPP4r and more about XMPP, this time it will be the other way around.

Now that we know that XMPP messages are XML bits of information exchanged between a client and a server via a TCP connection, we are more able to understand the purpose of XMPP4r.

What is XMPP4r?

Here is the most simple definition I came out with : XMPP4r is a ruby library that acts as a XMPP Client. Understand it that way and you won’t be confuse about what XMPP4r is supposed to do. Like any other jabber client (google talk, pidgin, etc), Xmpp4r sends, receives and manages XML messages called stanzas.

In a sense, XMPP4r is like GoogleTalk without the GUI. (And, of course, XMPP4r is not already implemented… you have to code the behavior of the client yourself). With GoogleTalk, you connect on a Jabber Server by pressing the connect button. With XMPP4r, you connect on a Jabber server that way :

  1. client = Client.new(JID::new("somejabberaccount@somewhere.com"))
  2. client.connect
  3. client.auth("my_extremely_secret_password")
  4. client.send(Presence.new.set_type(:available))

The first line simply creates a new instance of the Client class. This instance represents the user itself. The 2nd line tries to establish a connection between the user and the Jabber server (somewhere.com). The 3rd line tries to authenticate the user using the following mechanisms.

  • SASL DIGEST-MD5
  • SASL PLAIN
  • Non-SASL digest

Now about the 4th line. Remember when I talked about stanzas in the first part? Well, at the 4th line, we sent our first stanza to our Jabber Server… a presence stanza. We did this because we want our server to know we are there. That way, everyone in our buddy list will know we are online and ready to chat!

Sending messages
So, what now? You are online, fine… but how can you exchange messages with people? That’s pretty simple…

  1. msg = Message::new("john@somejabberserver.com", "Hello… John?")
  2. msg.type=:chat
  3. client.send(msg)

Important note : You have to set the message type to “chat” because some clients will react differently depending of the message type. A Gajim user will hate you if you send him messages with a message type of “normal”, because Gajim popups a new window for every single “normal” messages it receives. On the other hand, it uses the same window for “chat” messages coming from the same user.

Ok so we know how to send a message to someone, but what if that someone is not in our buddy list? Well, simply put, it will not work. XMPP just doesn’t allow this and we all agree that it’s a good thing. Who like to be spammed?

Say we want to add john@someserver.com to our buddy list, we do :

  1. pres = Presence.new.set_type(:subscribe).set_to("john@someserver.com")
  2. client.send(pres)

Ok so we sent our request… what about the response? Fortunately, the line above won’t wait for a result. As you can imagine, it could get pretty long… we don’t control the answer of the person at the other end after all. This leads me to talk to you about a key feature of xmpp4r : callbacks.

Registering Callbacks

Let’s get back to our subscription request we sent to John earlier. Since we said that our line of code would NOT wait for a response, we need some other way to get that response when it will come. (that’s pretty much what callbacks are for, right?)

Because we sent a subscription request, the callback “add_update_callback” will be called as soon as the user at the other end will reply. If you want to be notified when this happens, you have to register to this callback :

  1. client.add_update_callback do |presence|
  2.   if presence.from == "john@someserver.com" && presence.ask == :subscribe
  3.     client.send(presence.from, "I am so very happy you have accept my request John, you rock! I will spam you for the rest of my life, but I know you will understand because I feel we do ‘connect’")
  4.   end
  5. end

Xmpp4r provides callbacks for a lot of purposes. Now if I want to be notified of the messages sent to us by others, what do I have to use? The answer is add_message_callback!

  1. client.add_message_callback do |m|
  2. if m.from == "john@someserver.com"
  3.   #oh great! I have received a response from my new friend Johh! Better see what he says.
  4.    m.body
  5. # output : Listen, you moron. I don’t know who you are nor why you are so enthusiast about speaking with someone you don’t know, but I strongly suggest you to get a life. Stop or I remove you from my buddy list, FOREVER!
  6. end

There is also a very useful callback that lets you know when the availability of someone in your buddy list change.

  1. client.add_presence_callback do |old_presence, new_presence|
  2. if new_presence.from == "john@someserver.com" && new_presence.show == :dnd
  3.                 msg = Message::new("john@someserver.com", "John… I know you don’t want to be bothered (dnd = do not disturb) but I just wanted to say HI anyway! I read your previous message and I sill think we do ‘connect’. I just think you have trouble assuming your sensibility"
  4.                 msg.type=:chat
  5.                 client.send(msg)
  6. end

Note that the various callbacks in xmpp4r run within their own thread.

Well, this is the end of the 2nd part. Next part should be about the Roster helper and I also want to criticize Xmpp4r about his stability (lack of) and somewhat incomplete documentation.

Woooh! Silly me… here is an important update

Don Park and Nilu had problems with this tutorial… and it’s probably because I forgot to talk about an essential part : The subscription. To receive messages from others, you have to accept their subscription requests first! Here is how

  1. require ‘xmpp4r/roster’ (a roster is an object representing your buddy list)
  2. roster = Roster::Helper.new(client)
  3. Implement the add_subscription_request_callback that way (if you want to accept everyone) :
    1. roster.add_subscription_request_callback do |item,pres| 
    2.   roster.accept_subscription(pres.from)
    3. end

You should be fine now.. sorry about that

UPDATE October 21st 2008
Some interesting remarks in the comments deserved a proper update :

Nilu,

I’m sure you have your answer already, but let me explain the “running in their own thread” thing :

The callbacks are invoked in the context of the “parser thread” which is the one doing the actual “listening” of the the XML stream. When something happens in the parser thread (e.g. message stanza received), callbacks are invoked from there. So the code you have in your various callbacks are effectively executed in the parser thread context.

Like Leonardo wrote, you have to write Thread.stop in your main thread when you have set all your callbacks and other initialization stuff.

It might sound confusing, but you have to write Thread.stop to keep the current thread alive. Thread.stop will simply put the current thread into sleep mode and leave all the scheduling time to the other threads (the parser thread in our case). If you don’t write Thread.stop, everything ends when the main thread has nothing left to do.

Bookmark this post : These icons link to social bookmarking sites where readers can share and discover new web pages.
  • DZone
  • Reddit
  • del.icio.us
  • Digg
  • Furl
  • Technorati
  • StumbleUpon
Rate this post :
1 Star2 Stars3 Stars4 Stars5 Stars (12 votes, average: 4.83 out of 5)
Loading ... Loading ...
Posted under : In depth
28 Comments
MyAvatars 0.2

It was completly clear! I’m glad you write about XMPP4R because nobody wants to talk about it… almost no documentation, no examples and no blog posts. Is XMPP4R taboo?

Comment by : Dan Simard
— January 29, 2008 @ 10:18 am

MyAvatars 0.2

[…] IM Integration With XMPP4r : Part 2 (tags: ruby jabber programming) […]

— January 29, 2008 @ 8:21 pm

MyAvatars 0.2

so cool~ thx~

Comment by : IceskYsl
— January 29, 2008 @ 9:43 pm

MyAvatars 0.2

I don’t know if XMPP4R is taboo, but I am interrested by this project. A bit of documentation would be welcome.

Comment by : Stephane Wirtel
— January 30, 2008 @ 5:02 am

Comment by : IceskYsl
— January 30, 2008 @ 6:03 am

MyAvatars 0.2

Hey, thanks a lot. I’ve coincidentally been playing around with this a bunch. I’ve been having a problem with status synchronization with Google Talk. For example, say I want to connect, get my status, message it to a test account, then update it. I can’t seem to get the changes to apply if I login to Google Talk from another client (say GMail) Any ideas?

Comment by : Tyler
— January 30, 2008 @ 3:33 pm

MyAvatars 0.2

@Dan,

Yeah sometimes I wonder if Xmpp4r is taboo too… no one wants to talk about it! I have to say it is not the most actively supported gem around. I’m currently seriously thinking about writing my own for XMPP. Next step is to stop thinking about it and to actually do it :)

@Tyler,

Are you talking about the status itself (busy, available, etc) or the status message? We have experienced problems trying to set the status message with XMPP4r and we have simply stopped investigating on the issue.

If you are speaking about the status itself that doesn’t update… well I’m not quite sure about the source of the problem. Maybe it has something to do with the fact that you are trying to set your status manually (technically it should work but there might be a bug in xmpp4r). Could you send me the code you are using to accomplish what you want? I would be more able to help you with your problem.

Comment by : Frank
— January 31, 2008 @ 9:12 am

MyAvatars 0.2

Ruby Fleebie » IM Integration With XMPP4r : Part 2…

Ruby Fleebie » IM Integration With XMPP4r : Part 2…

— January 31, 2008 @ 12:15 pm

MyAvatars 0.2

Frank,

It’s great that you’re writing about this. Both were excellent articles and I hope you’ll keep it up.

Comment by : Mislav
— February 4, 2008 @ 1:32 am

MyAvatars 0.2

@Mislav,

I’m glad you like the articles. There will be a 3rd part for sure (that might be the final one, but I’m not sure of yet).

I really think IM integration is not used enough in today web applications. Some people see it as a gimmick, but to me nothing could be farther from the truth.

Thanks for your comment

Comment by : Frank
— February 4, 2008 @ 12:21 pm

MyAvatars 0.2

[…] XMPP4R to integrate with Instant Messaging systems. The first part is a basic overview, and the second part is a basic demo of creating an XMPP client. Frank expects to continue the series on his […]


MyAvatars 0.2

mangled resource names?
i connect with
jid = JID::new(’me@gmail.com/yin’)
client = Client::new(jid)
client.connect(’talk.google.com’)
client.auth(password)
it took a while to figure out how to connect to talk.google.com instead of gmail.com

and send with
to = “me@gmail.com/yang”
subject = “from yin to yang”
body = “help”
m = Message::new(to, body).set_type(:normal).set_id(’1′).set_subject(subject)
puts “sending: #{m.inspect}”
client.send m

i have a ‘yang’ version of the same program to receive the message. yang never gets the message and my pidgin IM program pops up with the “help” message from
me@gmail.com/yin04223891

do you know whats with the extra digits in the resource name?

Comment by : Don Park
— March 22, 2008 @ 12:39 pm

MyAvatars 0.2

I was trying to implement a chat client using this tutorial but it seems that my messages do not get recieved. I am using a local host for this. The client seems to send the messages (at least it doesn’t give me any errors) but when i run an another instance of the program and try to recieve messages, the messages do not show. Any ideas as to why this may be? Thank you in advance for any help

Comment by : Nilu
— April 10, 2008 @ 7:09 am

MyAvatars 0.2

@Don Park,

Sorry for the late answer. The digits after the resource name is an auto-generated ID… you do not have control over it. I believe it gets assigned by your Jabber server (in this case, talk.google.com).

Now about the problem you are facing, I realize I forgot to include something important in my article..shame on me. You have to subscribe to the other contact. I update the original post, look above.

@Nilu,

It is probably my fault for that again! I forgot to talk about the subscription part.. look at the end of this post… I wrote an update. I’m sorry about that

Comment by : Frank
— April 10, 2008 @ 7:56 am

MyAvatars 0.2

Thanks for the update! :D

Comment by : Nilu
— April 10, 2008 @ 7:06 pm

MyAvatars 0.2

I also found out that Client class does not contain ‘add_update_callback’ the Rsoter::Helper class does…

Comment by : Nilu
— April 10, 2008 @ 7:22 pm

MyAvatars 0.2

You said that the callbacks are in a separate thread, so then how will the main thread (which is the client) actually receive the message? How would it communicate with the client? I don’t seem to be able to receive messages, although i can send them successfully even when i use the roster.

Comment by : Nilu
— May 8, 2008 @ 6:28 am

MyAvatars 0.2

@Nilu: I had the same problem as you. Looking at the examples that came with the debian package libxmpp4r-ruby-doc, I found this:

Thread.stop
client.close

next to the end of the script, after you set your callbacks. Works for me. See echo_threaded.rb for more.

Comment by : Leonardo Boiko
— June 24, 2008 @ 10:08 pm

MyAvatars 0.2

Well … Sometime a go , I make a dirty jum to ruby … specialy xmpp4r.
Unfortunately my work is gone, the HD formated.
But here is the story about it :
Note that I’m a dumb lazy programmer with a bunch of dream.
I want to send the output of ttyctrl (http://ttyctrl.sourceforge.net/) at a station to another remote station, so I :
1. Install ttyctrl
2. Write a XMPP4R script, add a socket (udp) server to it
3. write a ruby script that can talk udp to script #2
4. set ttyctrl to call script#3 with it’s as parameter
5. When script#2 receive msg from script#3, it send ttyctrl output to another jabber client , which is me + pidgin.

Note : Script#2 and Script#3 is in the same host

This way, I can use XMPP for 2-way comunication between any application or any embeded device, regardless of excessive bandwidth usage caused by xml overhead.

Comment by : bino
— July 23, 2008 @ 3:43 am

MyAvatars 0.2

Thanks for this article, this is so nice to read some good documentation about xmpp4r.

However, there are some little mistakes that i wanted to point out here.
Well I pasted the codes right from the web site and here was the persisting error that kept coming at me when trying to execute the script:

Jabber_test_ruby.rb:81: syntax error, unexpected ‘}’, expecting kEND
Jabber_test_ruby.rb:92: syntax error, unexpected $end, expecting kEND
Tk.mainloop
^
it’s just because you forget to write the “end”s closing block of the callback methods(not all of them).

Anyway keep up the good work, I’ll come back here.

Comment by : Nakyss
— August 3, 2008 @ 4:57 pm

MyAvatars 0.2

Nilu,

I’m sure you have your answer already, but let me explain the “running in their own thread” thing :

The callbacks are invoked in the context of the “parser thread” which is the one doing the actual “listening” of the the XML stream. When something happens in the parser thread (e.g. message stanza received), callbacks are invoked from there. So the code you have in your various callbacks are effectively executed in another thread context.

Like Leonardo wrote, you have to write Thread.stop in your main thread when you have set all your callbacks and other initialization stuff.

It might sound confusing, but you have to write Thread.stop to keep the current thread alive. Thread.stop simply put the current thread into sleep mode and leave all the scheduling time to the other threads (the parser thread in our case). If you don’t write Thread.stop, everything ends when the main thread has nothing left to do.

I have updated the original post.

Comment by : Frank
— October 21, 2008 @ 4:40 pm

MyAvatars 0.2

[…] isso recomendo algumas leituras prévias sobre XMPP4R (a documentação oficial, este tutorial, e sua segunda parte) e de Rails (recomendo os tutoriais e screencasts do Fábio Akita, no seu blog, o AkitaOnRails). […]

— November 15, 2008 @ 12:35 pm

MyAvatars 0.2

Thanks,it’s so powerful!

Comment by : Hooopo
— February 26, 2009 @ 4:41 am

MyAvatars 0.2

Thanks for the useful site.Keep up the good work.God bless you and keep you.

Comment by : bishkekblog
— April 3, 2009 @ 10:36 am

MyAvatars 0.2

Thanks for the interesting and informative site. That’s definitely what I’ve been looking for.

Comment by : worldround
— April 3, 2009 @ 7:51 pm

MyAvatars 0.2

So how would one change the port on which this example connects on ie for communications with a TCP proxy or socks proxy.(-L or -D options in openssh) or say port 9050 for operation as a tor hidden server with OTR support added by otr-proxy www.cypherpunks.ca/otr/

Comment by : ruby beginner
— June 16, 2009 @ 3:30 am

MyAvatars 0.2

how do you send messages to the jabber chatroom using xmpp4r

Comment by : ryan
— July 5, 2009 @ 1:49 pm

MyAvatars 0.2

Presence seems to be mostly empty when getting a roster. Any ideas on how to get all the presence with the roster call?

Comment by : Art
— July 5, 2009 @ 11:07 pm




Leave a comment
Name (required)
Email (will not be publish) (required)
Website