Class: Jabber::Stream

Class | Jabber::Stream
In: | lib/xmpp4r/stream.rb
Parent: | Object

The stream class manages a connection stream (a file descriptor using which XML messages are read and sent)

You may register callbacks for the three Jabber stanzas (message, presence and iq) and use the send and send_with_id methods.

To ensure the order of received stanzas, callback blocks are launched in the parser thread. If further blocking operations are intended in those callbacks, run your own thread there.

### Methods

add_iq_callback add_message_callback add_presence_callback add_stanza_callback add_xml_callback close close! delete_iq_callback delete_message_callback delete_presence_callback delete_stanza_callback delete_xml_callback is_connected? is_disconnected? new on_exception parse_failure parser_end receive send send_data send_with_id start stop

### Classes and Modules

Class Jabber::Stream::ThreadBlock

### Constants

DISCONNECTED | = | 1
CONNECTED | = | 2

### Attributes

fd | [R] | file descriptor used
status | [R] | connection status

### Public Class methods

new()

Initialize a new stream

[Source]

        # File lib/xmpp4r/stream.rb, line 40
    40:     def initialize
    41:       @fd = nil
    42:       @status = DISCONNECTED
    43:       @xmlcbs = CallbackList.new
    44:       @stanzacbs = CallbackList.new
    45:       @messagecbs = CallbackList.new
    46:       @iqcbs = CallbackList.new
    47:       @presencecbs = CallbackList.new
    48:       @send_lock = Mutex.new
    49:       @last_send = Time.now
    50:       @exception_block = nil
    51:       @threadblocks = []
    52:       @wakeup_thread = nil
    53:       @streamid = nil
    54:       @streamns = 'jabber:client'
    55:       @features_sem = Semaphore.new
    56:       @parser_thread = nil
    57:     end

### Public Instance methods

add_iq_callback(priority = 0, ref = nil, &block)

Adds a callback block to process received Iqs

priority: | [Integer] The callback‘s priority, the higher, the sooner
ref: | [String] The callback‘s reference
&block: | [Block] The optional block

[Source]

         # File lib/xmpp4r/stream.rb, line 473
    473:     def add_iq_callback(priority = 0, ref = nil, &block)
    474:       @iqcbs.add(priority, ref, block)
    475:     end

add_message_callback(priority = 0, ref = nil, &block)

Adds a callback block to process received Messages

priority: | [Integer] The callback‘s priority, the higher, the sooner
ref: | [String] The callback‘s reference
&block: | [Block] The optional block

[Source]

         # File lib/xmpp4r/stream.rb, line 419
    419:     def add_message_callback(priority = 0, ref = nil, &block)
    420:       @messagecbs.add(priority, ref, block)
    421:     end

add_presence_callback(priority = 0, ref = nil, &block)

Adds a callback block to process received Presences

priority: | [Integer] The callback‘s priority, the higher, the sooner
ref: | [String] The callback‘s reference
&block: | [Block] The optional block

[Source]

         # File lib/xmpp4r/stream.rb, line 455
    455:     def add_presence_callback(priority = 0, ref = nil, &block)
    456:       @presencecbs.add(priority, ref, block)
    457:     end

add_stanza_callback(priority = 0, ref = nil, &block)

Adds a callback block to process received Stanzas

priority: | [Integer] The callback‘s priority, the higher, the sooner
ref: | [String] The callback‘s reference
&block: | [Block] The optional block

[Source]

         # File lib/xmpp4r/stream.rb, line 437
    437:     def add_stanza_callback(priority = 0, ref = nil, &block)
    438:       @stanzacbs.add(priority, ref, block)
    439:     end

add_xml_callback(priority = 0, ref = nil, &block)

Adds a callback block to process received XML messages

priority: | [Integer] The callback‘s priority, the higher, the sooner
ref: | [String] The callback‘s reference
&block: | [Block] The optional block

[Source]

         # File lib/xmpp4r/stream.rb, line 401
    401:     def add_xml_callback(priority = 0, ref = nil, &block)
    402:       @xmlcbs.add(priority, ref, block)
    403:     end

close()

Closes the connection to the Jabber service

[Source]

         # File lib/xmpp4r/stream.rb, line 487
    487:     def close
    488:       close!
    489:     end

close!()

[Source]

         # File lib/xmpp4r/stream.rb, line 491
    491:     def close!
    492:       @parser_thread.kill if @parser_thread
    493:       @fd.close if @fd and !@fd.closed?
    494:       @status = DISCONNECTED
    495:     end

delete_iq_callback(ref)

Delete an Iq callback

ref: | [String] The reference of the callback to delete

[Source]

         # File lib/xmpp4r/stream.rb, line 482
    482:     def delete_iq_callback(ref)
    483:       @iqcbs.delete(ref)
    484:     end

delete_message_callback(ref)

Delete an Message callback

ref: | [String] The reference of the callback to delete

[Source]

         # File lib/xmpp4r/stream.rb, line 427
    427:     def delete_message_callback(ref)
    428:       @messagecbs.delete(ref)
    429:     end

delete_presence_callback(ref)

Delete a Presence callback

ref: | [String] The reference of the callback to delete

[Source]

         # File lib/xmpp4r/stream.rb, line 463
    463:     def delete_presence_callback(ref)
    464:       @presencecbs.delete(ref)
    465:     end

delete_stanza_callback(ref)

Delete a Stanza callback

ref: | [String] The reference of the callback to delete

[Source]

         # File lib/xmpp4r/stream.rb, line 445
    445:     def delete_stanza_callback(ref)
    446:       @stanzacbs.delete(ref)
    447:     end

delete_xml_callback(ref)

Delete an XML-messages callback

ref: | [String] The reference of the callback to delete

[Source]

         # File lib/xmpp4r/stream.rb, line 409
    409:     def delete_xml_callback(ref)
    410:       @xmlcbs.delete(ref)
    411:     end

is_connected?()

Returns if this connection is connected to a Jabber service

return: | [Boolean] Connection status

[Source]

         # File lib/xmpp4r/stream.rb, line 155
    155:     def is_connected?
    156:       return @status == CONNECTED
    157:     end

is_disconnected?()

Returns if this connection is NOT connected to a Jabber service

return: | [Boolean] Connection status

[Source]

         # File lib/xmpp4r/stream.rb, line 163
    163:     def is_disconnected?
    164:       return @status == DISCONNECTED
    165:     end

on_exception(&block)

Mounts a block to handle exceptions if they occur during the poll send. This will likely be the first indication that the socket dropped in a Jabber Session.

The block has to take three arguments:

- the Exception
- the Jabber::Stream object (self)
- a symbol where it happened, namely :start, :parser, :sending and :end

[Source]

         # File lib/xmpp4r/stream.rb, line 112
    112:     def on_exception(&block)
    113:       @exception_block = block
    114:     end

parse_failure(e)

This method is called by the parser when a failure occurs

[Source]

         # File lib/xmpp4r/stream.rb, line 118
    118:     def parse_failure(e)
    119:       Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
    120:
    121:       # A new thread has to be created because close will cause the thread
    122:       # to commit suicide(???)
    123:       if @exception_block
    124:         # New thread, because close will kill the current thread
    125:         Thread.new do
    126:           Thread.current.abort_on_exception = true
    127:           close
    128:           @exception_block.call(e, self, :parser)
    129:         end
    130:       else
    131:         Jabber::debuglog "Stream#parse_failure was called by XML parser. Dumping " +
    132:           "backtrace...\n" + e.exception + "\n#{e.backtrace.join("\n")}"
    133:         close
    134:         raise
    135:       end
    136:     end

parser_end()

This method is called by the parser upon receiving `</stream:stream>`

[Source]

         # File lib/xmpp4r/stream.rb, line 140
    140:     def parser_end
    141:       if @exception_block
    142:         Thread.new do
    143:           Thread.current.abort_on_exception = true
    144:           close
    145:           @exception_block.call(nil, self, :close)
    146:         end
    147:       else
    148:         close
    149:       end
    150:     end

receive(element)

Processes a received REXML::Element and executes registered thread blocks and filters against it.

element: | [REXML::Element] The received element

[Source]

         # File lib/xmpp4r/stream.rb, line 172
    172:     def receive(element)
    173:       Jabber::debuglog("RECEIVED:\n#{element.to_s}")
    174:
    175:       if element.namespace('').to_s == '' # REXML namespaces are always strings
    176:         element.add_namespace(@streamns)
    177:       end
    178:
    179:       case element.prefix
    180:       when 'stream'
    181:         case element.name
    182:           when 'stream'
    183:             stanza = element
    184:             @streamid = element.attributes['id']
    185:             @streamns = element.namespace('') if element.namespace('')
    186:
    187:             # Hack: component streams are basically client streams.
    188:             # Someday we may want to create special stanza classes
    189:             # for components/s2s deriving from normal stanzas but
    190:             # posessing these namespaces
    191:             @streamns = 'jabber:client' if @streamns == 'jabber:component:accept'
    192:
    193:             unless element.attributes['version']  # isn't XMPP compliant, so
    194:               Jabber::debuglog("FEATURES: server not XMPP compliant, will not wait for features")
    195:               @features_sem.run                   # don't wait for <stream:features/>
    196:             end
    197:           when 'features'
    198:             stanza = element
    199:             element.each { |e|
    200:               if e.name == 'mechanisms' and e.namespace == 'urn:ietf:params:xml:ns:xmpp-sasl'
    201:                 e.each_element('mechanism') { |mech|
    202:                   @stream_mechanisms.push(mech.text)
    203:                 }
    204:               else
    205:                 @stream_features[e.name] = e.namespace
    206:               end
    207:             }
    208:             Jabber::debuglog("FEATURES: received")
    209:             @features_sem.run
    210:           else
    211:             stanza = element
    212:         end
    213:       else
    214:         # Any stanza, classes are registered by XMPPElement::name_xmlns
    215:         begin
    216:           stanza = XMPPStanza::import(element)
    217:         rescue NoNameXmlnsRegistered
    218:           stanza = element
    219:         end
    220:       end
    221:
    222:       # Iterate through blocked threads (= waiting for an answer)
    223:       #
    224:       # We're dup'ping the @threadblocks here, so that we won't end up in an
    225:       # endless loop if Stream#send is being nested. That means, the nested
    226:       # threadblock won't receive the stanza currently processed, but the next
    227:       # one.
    228:       threadblocks = @threadblocks.dup
    229:       threadblocks.each { |threadblock|
    230:         exception = nil
    231:         r = false
    232:         begin
    233:           r = threadblock.call(stanza)
    234:         rescue Exception => e
    235:           exception = e
    236:         end
    237:
    238:         if r == true
    239:           @threadblocks.delete(threadblock)
    240:           threadblock.wakeup
    241:           return
    242:         elsif exception
    243:           @threadblocks.delete(threadblock)
    244:           threadblock.raise(exception)
    245:         end
    246:       }
    247:
    248:       Jabber::debuglog("PROCESSING:\n#{stanza.to_s} (#{stanza.class})")
    249:       return true if @xmlcbs.process(stanza)
    250:       return true if @stanzacbs.process(stanza)
    251:       case stanza
    252:       when Message
    253:         return true if @messagecbs.process(stanza)
    254:       when Iq
    255:         return true if @iqcbs.process(stanza)
    256:       when Presence
    257:         return true if @presencecbs.process(stanza)
    258:       end
    259:     end

send(xml, &block)

Sends XML data to the socket and (optionally) waits to process received data.

Do not invoke this in a callback but in a seperate thread because we may not suspend the parser-thread (in whose context callbacks are executed).

xml: | [String] The xml data to send
&block: | [Block] The optional block

[Source]

         # File lib/xmpp4r/stream.rb, line 306
    306:     def send(xml, &block)
    307:       Jabber::debuglog("SENDING:\n#{xml}")
    308:       @threadblocks.unshift(threadblock = ThreadBlock.new(block)) if block
    309:       begin
    310:         # Temporarily remove stanza's namespace to
    311:         # reduce bandwidth consumption
    312:         if xml.kind_of? XMPPStanza and xml.namespace == 'jabber:client'
    313:           xml.delete_namespace
    314:           send_data(xml.to_s)
    315:           xml.add_namespace(@streamns)
    316:         else
    317:           send_data(xml.to_s)
    318:         end
    319:       rescue Exception => e
    320:         Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
    321:
    322:         if @exception_block
    323:           Thread.new do
    324:             Thread.current.abort_on_exception = true
    325:             close!
    326:             @exception_block.call(e, self, :sending)
    327:           end
    328:         else
    329:           Jabber::debuglog "Exception caught while sending! (#{e.class})\n#{e.backtrace.join("\n")}"
    330:           close!
    331:           raise
    332:         end
    333:       end
    334:       # The parser thread might be running this (think of a callback running send())
    335:       # If this is the case, we mustn't stop (or we would cause a deadlock)
    336:       if block and Thread.current != @parser_thread
    337:         threadblock.wait
    338:       elsif block
    339:         Jabber::debuglog("WARNING:\nCannot stop current thread in Jabber::Stream#send because it is the parser thread!")
    340:       end
    341:     end

send_data(data)

[Source]

         # File lib/xmpp4r/stream.rb, line 288
    288:     def send_data(data)
    289:       @send_lock.synchronize do
    290:         @last_send = Time.now
    291:         @fd << data
    292:         @fd.flush
    293:       end
    294:     end

send_with_id(xml) {|received| ...}

Send an XMMP stanza with an Jabber::XMPPStanza#id. The id will be generated by Jabber::IdGenerator if not already set.

The block will be called once: when receiving a stanza with the same Jabber::XMPPStanza#id. There is no need to return true to complete this! Instead the return value of the block will be returned. This is a direct result of unique request/response stanza identification via the id attribute.

The block may be omitted. Then, the result will be the response stanza.

Be aware that if a stanza with `type=‘error‘` is received the function does not yield but raises an ServerError with the corresponding error element.

Please see Stream#send for some implementational details.

Please read the note about nesting at Stream#send

xml: | [XMPPStanza]

[Source]

         # File lib/xmpp4r/stream.rb, line 364
    364:     def send_with_id(xml, &block)
    365:       if xml.id.nil?
    366:         xml.id = Jabber::IdGenerator.instance.generate_id
    367:       end
    368:
    369:       res = nil
    370:       error = nil
    371:       send(xml) do |received|
    372:         if received.kind_of? XMPPStanza and received.id == xml.id
    373:           if received.type == :error
    374:             error = (received.error ? received.error : ErrorResponse.new)
    375:             true
    376:           elsif block_given?
    377:             res = yield(received)
    378:             true
    379:           else
    380:             res = received
    381:             true
    382:           end
    383:         else
    384:           false
    385:         end
    386:       end
    387:
    388:       unless error.nil?
    389:         raise ServerError.new(error)
    390:       end
    391:
    392:       res
    393:     end

start(fd)

Start the XML parser on the fd

[Source]

        # File lib/xmpp4r/stream.rb, line 61
    61:     def start(fd)
    62:       @stream_mechanisms = []
    63:       @stream_features = {}
    64:
    65:       @fd = fd
    66:       @parser = StreamParser.new(@fd, self)
    67:       @parser_thread = Thread.new do
    68:         Thread.current.abort_on_exception = true
    69:         begin
    70:           @parser.parse
    71:           Jabber::debuglog("DISCONNECTED\n")
    72:
    73:           if @exception_block
    74:             Thread.new { close!; @exception_block.call(nil, self, :disconnected) }
    75:           else
    76:             close!
    77:           end
    78:         rescue Exception => e
    79:           Jabber::debuglog("EXCEPTION:\n#{e.class}\n#{e.message}\n#{e.backtrace.join("\n")}")
    80:
    81:           if @exception_block
    82:             Thread.new do
    83:               Thread.current.abort_on_exception = true
    84:               close
    85:               @exception_block.call(e, self, :start)
    86:             end
    87:           else
    88:             Jabber::debuglog "Exception caught in Parser thread! (#{e.class})\n#{e.backtrace.join("\n")}"
    89:             close!
    90:             raise
    91:           end
    92:         end
    93:       end
    94:
    95:       @status = CONNECTED
    96:     end

stop()

[Source]

         # File lib/xmpp4r/stream.rb, line 98
     98:     def stop
     99:       @parser_thread.kill
    100:       @parser = nil
    101:     end

[Validate]