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'
@@store =
nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

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

Called by EM.connect to initialize stream variables



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/blather/stream.rb', line 135

def initialize(client, jid, pass, connect_timeout = nil)
  super()

  @error = nil
  @receiver = @client = client

  self.jid = jid
  @to = self.jid.domain
  @password = pass
  @connect_timeout = connect_timeout || 180
end

Instance Attribute Details

#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 = nil) ⇒ 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



112
113
114
115
116
# File 'lib/blather/stream.rb', line 112

def self.connect(host, port, conn, client, jid, pass, connect_timeout = nil)
  EM.connect host, port, conn, client, jid, pass, connect_timeout
rescue NoConnection
  false
end

.start(client, jid, pass, host = nil, port = nil, certs_directory = nil, connect_timeout = nil) ⇒ 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



75
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
# File 'lib/blather/stream.rb', line 75

def self.start(client, jid, pass, host = nil, port = nil, certs_directory = nil, connect_timeout = nil)
  jid = JID.new jid
  port ||= 5222
  if certs_directory
    @@store = CertStore.new(certs_directory)
  end
  if host
    connect host, port, self, client, jid, pass, connect_timeout
  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
    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) === 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



150
151
152
153
154
155
156
157
158
159
# File 'lib/blather/stream.rb', line 150

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
#      @keepalive = EM::PeriodicTimer.new(60) { send_data ' ' }
  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



208
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 208

def receive(node)
  Blather.log "RECEIVING (#{node.element_name}) #{node}"
  @node = 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
      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



163
164
165
166
167
168
169
170
# File 'lib/blather/stream.rb', line 163

def receive_data(data)
  @parser << data

rescue ParseError => e
  @error = e
  send "<stream:error><xml-not-well-formed xmlns='#{StreamError::STREAM_ERR_NS}'/></stream:error>"
  stop
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



127
128
129
130
131
# File 'lib/blather/stream.rb', line 127

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 if !@@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
# File 'lib/blather/stream.rb', line 195

def unbind
  raise NoConnection unless @inited
  raise ConnectionFailed unless @connected

  @connect_timer.cancel if @connect_timer
#      @keepalive.cancel
  @state = :stopped
  @client.receive_data @error if @error
  @client.unbind
end