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
# 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
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
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
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
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
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
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
Returns the total number of streams connected to the server.
# File lib/vines/router.rb, line 100 def size clients = @clients.values.inject(0) {|sum, arr| sum + arr.size } clients + @servers.size + @components.size end
Private Instance Methods
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 133 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
# File lib/vines/router.rb, line 157 def component_stream(to) @components.select do |stream| stream.ready? && stream.remote_domain == to.domain end.sample end
# File lib/vines/router.rb, line 153 def connection_to(to, from) component_stream(to) || server_stream(to, from) end
Return the bare JIDs from the list that are allowed to talk to the from
JID
.
# File lib/vines/router.rb, line 142 def filter_allowed(jids, from) from = JID.new(from) jids.flatten.map {|jid| JID.new(jid).bare } .select {|jid| @config.allowed?(jid, from) } end
# File lib/vines/router.rb, line 148 def proxies(*jids) return EMPTY unless @config.cluster? @config.cluster.remote_sessions(*jids) end
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 118 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
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 110 def send_pending(key, stream) @pending[key].each do |stanza| stream.write(stanza) end end
# File lib/vines/router.rb, line 163 def server_stream(to, from) @servers.select do |stream| stream.ready? && stream.remote_domain == to.domain && stream.domain == from.domain end.sample end
# File lib/vines/router.rb, line 171 def stream_type(connection) connection.stream_type.tap do |type| unless STREAM_TYPES.include?(type) raise ArgumentError, "unexpected stream type: #{type}" end end end