class Vines::Router

The router tracks all stream connections to the server for all clients, servers, and components. It sends stanzas to the correct stream based on the 'to' attribute. Router is a singleton, shared by all streams, that must be accessed with +Config#router+.

Constants

EMPTY
STREAM_TYPES

Public Class Methods

new(config) click to toggle source
# File lib/vines/router.rb, line 13
def initialize(config)
  @config = config
  @clients, @servers, @components = {}, [], []
  @pending = Hash.new {|h,k| h[k] = [] }
end

Public Instance Methods

<<(stream) click to toggle source

Add the connection to the routing table. The connection must return :client, :server, or :component from its stream_type method so the router can properly route stanzas to the stream.

# File lib/vines/router.rb, line 51
def <<(stream)
  case stream_type(stream)
  when :client then
    return unless stream.connected?
    jid = stream.user.jid.bare
    @clients[jid] ||= []
    @clients[jid] << stream
  when :server then @servers << stream
  when :component then @components << stream
  end
end
available_resources(*jids, from) click to toggle source

Returns streams for all available resources for this JID. A resource is marked available after it sends initial presence.

# File lib/vines/router.rb, line 34
def available_resources(*jids, from)
  clients(jids, from) do |stream|
    stream.available?
  end
end
connected_resources(jid, from, proxies=true) click to toggle source

Returns streams for all connected resources for this JID. A resource is considered connected after it has completed authentication and resource binding.

# File lib/vines/router.rb, line 22
def connected_resources(jid, from, proxies=true)
  jid, from = JID.new(jid), JID.new(from)
  return [] unless @config.allowed?(jid, from)

  local = @clients[jid.bare] || EMPTY
  local = local.select {|stream| stream.user.jid == jid } unless jid.bare?
  remote = proxies ? proxies(jid) : EMPTY
  [local, remote].flatten
end
delete(stream) click to toggle source

Remove the connection from the routing table.

# File lib/vines/router.rb, line 64
def delete(stream)
  case stream_type(stream)
  when :client then
    return unless stream.connected?
    jid = stream.user.jid.bare
    streams = @clients[jid] || []
    streams.delete(stream)
    @clients.delete(jid) if streams.empty?
  when :server then @servers.delete(stream)
  when :component then @components.delete(stream)
  end
end
interested_resources(*jids, from) click to toggle source

Returns streams for all interested resources for this JID. A resource is marked interested after it requests the roster.

# File lib/vines/router.rb, line 42
def interested_resources(*jids, from)
  clients(jids, from) do |stream|
    stream.interested?
  end
end
route(stanza) click to toggle source

Send the stanza to the appropriate remote server-to-server stream or an external component stream.

# File lib/vines/router.rb, line 79
def route(stanza)
  to, from = %w[to from].map {|attr| JID.new(stanza[attr]) }
  return unless @config.allowed?(to, from)
  key = [to.domain, from.domain]

  if stream = connection_to(to, from)
    stream.write(stanza)
  elsif @pending.key?(key)
    @pending[key] << stanza
  elsif @config.s2s?(to.domain)
    @pending[key] << stanza
    Vines::Stream::Server.start(@config, to.domain, from.domain) do |stream|
      stream ? send_pending(key, stream) : return_pending(key)
      @pending.delete(key)
    end
  else
    raise StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel')
  end
end
size() click to toggle source

Returns the total number of streams connected to the server.

# File lib/vines/router.rb, line 105
def size
  clients = @clients.values.inject(0) {|sum, arr| sum + arr.size }
  clients + @servers.size + @components.size
end
stream_by_id(id) click to toggle source

Return stream by id

# File lib/vines/router.rb, line 100
def stream_by_id(id)
  (@servers+@clients.values.flatten+@components).find {|stream| stream.id == id }
end

Private Instance Methods

clients(jids, from, &filter) click to toggle source

Return the client streams to which the from address is allowed to contact. Apply the filter block to each stream to narrow the results before returning the streams.

# File lib/vines/router.rb, line 138
def clients(jids, from, &filter)
  jids = filter_allowed(jids, from)
  local = @clients.values_at(*jids).compact.flatten.select(&filter)
  proxies = proxies(*jids).select(&filter)
  [local, proxies].flatten
end
component_stream(to) click to toggle source
# File lib/vines/router.rb, line 162
def component_stream(to)
  @components.select do |stream|
    stream.ready? && stream.remote_domain == to.domain
  end.sample
end
connection_to(to, from) click to toggle source
# File lib/vines/router.rb, line 158
def connection_to(to, from)
  component_stream(to) || server_stream(to, from)
end
filter_allowed(jids, from) click to toggle source

Return the bare JIDs from the list that are allowed to talk to the from JID.

# File lib/vines/router.rb, line 147
def filter_allowed(jids, from)
  from = JID.new(from)
  jids.flatten.map {|jid| JID.new(jid).bare }
    .select {|jid| @config.allowed?(jid, from) }
end
proxies(*jids) click to toggle source
# File lib/vines/router.rb, line 153
def proxies(*jids)
  return EMPTY unless @config.cluster?
  @config.cluster.remote_sessions(*jids)
end
return_pending(key) click to toggle source

Return all pending stanzas to their senders as remote-server-not-found errors. Called after a s2s stream has failed to connect.

# File lib/vines/router.rb, line 123
def return_pending(key)
  @pending[key].each do |stanza|
    to, from = JID.new(stanza['to']), JID.new(stanza['from'])
    xml = StanzaErrors::RemoteServerNotFound.new(stanza, 'cancel').to_xml
    if @config.component?(from)
      connection_to(from, to).write(xml) rescue nil
    else
      connected_resources(from, to).each {|c| c.write(xml) }
    end
  end
end
send_pending(key, stream) click to toggle source

Write all pending stanzas for this domain to the stream. Called after a s2s stream has successfully connected and we need to dequeue all stanzas we received while waiting for the connection to finish.

# File lib/vines/router.rb, line 115
def send_pending(key, stream)
  @pending[key].each do |stanza|
    stream.write(stanza)
  end
end
server_stream(to, from) click to toggle source
# File lib/vines/router.rb, line 168
def server_stream(to, from)
  @servers.select do |stream|
    stream.ready? &&
      stream.remote_domain == to.domain &&
        stream.domain == from.domain
  end.sample
end
stream_type(connection) click to toggle source
# File lib/vines/router.rb, line 176
def stream_type(connection)
  connection.stream_type.tap do |type|
    unless STREAM_TYPES.include?(type)
      raise ArgumentError, "unexpected stream type: #{type}"
    end
  end
end