class Blather::Stream
# A pure XMPP stream.
Blather::Stream
can be used to build your own handler system if Blather's doesn't suit your needs. It will take care of the entire connection process then start sending Stanza
objects back to the registered client.
The client you register with Blather::Stream
needs to implement the following methods:
-
post_init
(stream, jid = nil) Called after the stream has been initiated. @param [Blather::Stream] stream is the connected stream object @param [Blather::JID, nil] jid is the fullJID
as recognized by the server -
receive_data(stanza)
Called every time the stream receives a new stanza @param [Blather::Stanza] stanza a stanza object from the server -
unbind
Called when the stream is shutdown. This will be called regardless of which side shut the stream down.
@example Create a new stream and handle it with our own class
class MyClient attr :jid def post_init(stream, jid = nil) @stream = stream self.jid = jid p "Stream Started" end # Pretty print the stream def receive_data(stanza) pp stanza end def unbind p "Stream Ended" end def write(what) @stream.write what end end client = Blather::Stream.start MyClient.new, "jid@domain/res", "pass" client.write "[pure xml over the wire]"
Constants
- STREAM_NS
@private
Attributes
Public Class Methods
Attempt a connection Stream
will raise NoConnection
if it receives unbind
before post_init
this catches that and returns false prompting for another attempt @private
# File lib/blather/stream.rb, line 114 def self.connect(host, port, conn, client, jid, pass, connect_timeout, authcid) EM.connect host, port, conn, client, jid, pass, connect_timeout, authcid rescue NoConnection false end
Called by EM.connect to initialize stream variables @private
# File lib/blather/stream.rb, line 137 def initialize(client, jid, pass, connect_timeout = nil, authcid = nil) super() @error = nil @receiver = @client = client self.jid = jid @to = self.jid.domain @password = pass @connect_timeout = connect_timeout || 180 @authcid = authcid || self.jid.node end
Start the stream between client and server
@param [Object] client an object that will respond to post_init
, unbind
receive_data
@param [Blather::JID, to_s] jid the jid to authenticate with @param [String] pass the password to authenticate with @param [String, nil] host the hostname or IP to connect to. Default is to use the domain on the JID
@param [Fixnum, nil] port the port to connect on. Default is the XMPP default of 5222 @param [String, nil] certs the trusted cert store in pem format to verify communication with the server is trusted. @param [Fixnum, nil] connect_timeout the number of seconds for which to wait for a successful connection @param [Hash] opts options for modifying the connection @options opts [String] :authcid The authentication ID, which defaults to the node part of the specified JID
# File lib/blather/stream.rb, line 76 def self.start(client, jid, pass, host = nil, port = nil, certs_directory = nil, connect_timeout = nil, opts = {}) jid = JID.new jid port ||= 5222 if certs_directory @store = CertStore.new(certs_directory) end authcid = opts[:authcid] if host connect host, port, self, client, jid, pass, connect_timeout, authcid else require 'resolv' srv = [] Resolv::DNS.open do |dns| srv = dns.getresources( "_xmpp-client._tcp.#{jid.domain}", Resolv::DNS::Resource::IN::SRV ) end if srv.empty? connect jid.domain, port, self, client, jid, pass, connect_timeout, authcid else srv.sort! do |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) end srv.detect do |r| not connect(r.target.to_s, r.port, self, client, jid, pass, connect_timeout, authcid) === false end end end end
Public Instance Methods
Called when EM completes the connection to the server this kicks off the starttls/authorize/bind process @private
# File lib/blather/stream.rb, line 153 def connection_completed if @connect_timeout @connect_timer = EM::Timer.new @connect_timeout do raise ConnectionTimeout, "Stream timed out after #{@connect_timeout} seconds." unless started? end end @connected = true start end
Ensure the JID
gets attached to the client @private
# File lib/blather/stream.rb, line 237 def jid=(new_jid) Blather.log "USING JID: #{new_jid}" @jid = JID.new new_jid end
Called by EM after the connection has started @private
# File lib/blather/stream.rb, line 189 def post_init @inited = true end
Called by the parser with parsed nodes @private
# File lib/blather/stream.rb, line 209 def receive(node) Blather.log "RECEIVING (#{node.element_name}) #{node}" if node.namespace && node.namespace.prefix == 'stream' case node.element_name when 'stream' @state = :ready if @state == :stopped return when 'error' handle_stream_error node return when 'end' stop return when 'features' @state = :negotiating @receiver = Features.new( self, proc { ready! }, proc { |err| @error = err; stop } ) end end @receiver.receive_data node.to_stanza end
Called by EM with data from the wire @private
# File lib/blather/stream.rb, line 165 def receive_data(data) @parser << data rescue ParseError => e @error = e stop "<stream:error><xml-not-well-formed xmlns='#{StreamError::STREAM_ERR_NS}'/></stream:error>" end
Send data over the wire
@todo Queue if not ready
@param [#to_xml, to_s] stanza the stanza to send over the wire
# File lib/blather/stream.rb, line 129 def send(stanza) data = stanza.respond_to?(:to_xml) ? stanza.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML) : stanza.to_s Blather.log "SENDING: (#{caller[1]}) #{stanza}" EM.next_tick { send_data data } end
Called by EM to verify the peer certificate. If a certificate store directory has not been configured don't worry about peer verification. At least it is encrypted We Log the certificate so that you can add it to the trusted store easily if desired @private
# File lib/blather/stream.rb, line 176 def ssl_verify_peer(pem) # EM is supposed to close the connection when this returns false, # but it only does that for inbound connections, not when we # make a connection to another server. Blather.log "Checking SSL cert: #{pem}" return true unless @store @store.trusted?(pem).tap do |trusted| close_connection unless trusted end end
Called by EM when the connection is closed @private
# File lib/blather/stream.rb, line 195 def unbind raise NoConnection unless @inited raise ConnectionFailed unless @connected @parser.finish @connect_timer.cancel if @connect_timer @state = :stopped @client.receive_data @error if @error @client.unbind end
Protected Instance Methods
@private
# File lib/blather/stream.rb, line 253 def handle_stream_error(node) @error = StreamError.import(node) stop end
@private
# File lib/blather/stream.rb, line 259 def ready! @state = :started @receiver = @client @client.post_init self, @jid end
Stop the stream @private
# File lib/blather/stream.rb, line 245 def stop(error = nil) unless @state == :stopped @state = :stopped send "#{error}</stream:stream>" end end