Class: Blather::Stream

Inherits:
EventMachine::Connection
  • Object
show all
Defined in:
lib/blather/stream.rb,
lib/blather/stream/client.rb,
lib/blather/stream/parser.rb,
lib/blather/stream/features.rb,
lib/blather/stream/component.rb,
lib/blather/stream/features/tls.rb,
lib/blather/stream/features/sasl.rb,
lib/blather/stream/features/session.rb,
lib/blather/stream/features/register.rb,
lib/blather/stream/features/resource.rb

Overview

# 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 full JID 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.

Examples:

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]"

Direct Known Subclasses

Client, Component

Defined Under Namespace

Classes: Client, Component, ConnectionFailed, ConnectionTimeout, Features, NoConnection, Parser, Register, Resource, SASL, Session, TLS

Constant Summary collapse

STREAM_NS =
'http://etherx.jabber.org/streams'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client, jid, pass, connect_timeout = nil, authcid = nil) ⇒ Stream

Called by EM.connect to initialize stream variables



137
138
139
140
141
142
143
144
145
146
147
148
# 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

Instance Attribute Details

#authcidObject (readonly)

Returns the value of attribute authcid.



59
60
61
# File 'lib/blather/stream.rb', line 59

def authcid
  @authcid
end

#jidObject

Returns the value of attribute jid.



59
60
61
# File 'lib/blather/stream.rb', line 59

def jid
  @jid
end

#passwordObject

Returns the value of attribute password.



58
59
60
# File 'lib/blather/stream.rb', line 58

def password
  @password
end

Class Method Details

.connect(host, port, conn, client, jid, pass, connect_timeout, authcid) ⇒ Object

Attempt a connection Stream will raise NoConnection if it receives #unbind before #post_init this catches that and returns false prompting for another attempt



114
115
116
117
118
# 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

.start(client, jid, pass, host = nil, port = nil, certs_directory = nil, connect_timeout = nil, opts = {}) ⇒ Object

Start the stream between client and server

#unbind #receive_data to use the domain on the JID default of 5222 communication with the server is trusted.

Parameters:

  • client (Object)

    an object that will respond to #post_init,

  • jid (Blather::JID, #to_s)

    the jid to authenticate with

  • pass (String)

    the password to authenticate with

  • host (String, nil) (defaults to: nil)

    the hostname or IP to connect to. Default is

  • port (Fixnum, nil) (defaults to: nil)

    the port to connect on. Default is the XMPP

  • certs (String, nil)

    the trusted cert store in pem format to verify

  • connect_timeout (Fixnum, nil) (defaults to: nil)

    the number of seconds for which to wait for a successful connection

  • opts (Hash) (defaults to: {})

    options for modifying the connection



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# 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

Instance Method Details

#connection_completedObject

Called when EM completes the connection to the server this kicks off the starttls/authorize/bind process



153
154
155
156
157
158
159
160
161
# 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

#post_initObject

Called by EM after the connection has started



189
190
191
# File 'lib/blather/stream.rb', line 189

def post_init
  @inited = true
end

#receive(node) ⇒ Object

Called by the parser with parsed nodes



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# 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

#receive_data(data) ⇒ Object

Called by EM with data from the wire



165
166
167
168
169
170
# 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(stanza) ⇒ Object

TODO:

Queue if not ready

Send data over the wire

Parameters:

  • stanza (#to_xml, #to_s)

    the stanza to send over the wire



129
130
131
132
133
# 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

#ssl_verify_peer(pem) ⇒ Object

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



176
177
178
179
180
181
182
183
184
185
# 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

#unbindObject

Called by EM when the connection is closed

Raises:



195
196
197
198
199
200
201
202
203
204
205
# 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