class Vines::Stream::Http

Attributes

session[RW]

Public Class Methods

new(config) click to toggle source
Calls superclass method Vines::Stream::Client::new
# File lib/vines/stream/http.rb, line 8
def initialize(config)
  super
  @session = Http::Session.new(self)
end

Public Instance Methods

create_parser() click to toggle source

Override Stream#create_parser to provide an HTTP parser rather than a Nokogiri XML parser.

Returns nothing.

# File lib/vines/stream/http.rb, line 17
def create_parser
  @parser = ::Http::Parser.new.tap do |parser|
    body = ''
    parser.on_body = proc {|data| body << data }
    parser.on_message_complete = proc do
      process_request(Request.new(self, @parser, body))
      body = ''
    end
  end
end
parse_body(body) click to toggle source

Parse the one or more stanzas from a single body element. BOSH clients buffer stanzas sent in quick succession, and send them as a bundle, to save on the request/response cycle.

TODO This parses the XML again just to strip namespaces. Figure out Nokogiri namespace handling instead.

body - The XML::Node containing the BOSH `body` element.

Returns an Array of XML::Node stanzas.

# File lib/vines/stream/http.rb, line 94
def parse_body(body)
  body.namespace = nil
  body.elements.map do |node|
    Nokogiri::XML(node.to_s.sub(' xmlns="jabber:client"', '')).root
  end
end
process_request(request) click to toggle source
# File lib/vines/stream/http.rb, line 48
def process_request(request)
  if request.method == 'POST'
    if request.path == self.bind && request.options?
      request.reply_to_options
    elsif request.path == self.bind
      body = Nokogiri::XML(request.body).root
      if session = Sessions[body['sid']]
        @session = session
      else
        @session = Http::Session.new(self)
      end
      @session.request(request)
      @nodes.push(body)
    end
  else
    request.reply('It works!', 'text/plain')
  end
end
start(node) click to toggle source
# File lib/vines/stream/http.rb, line 101
def start(node)
  domain, type, hold, wait, rid = %w[to content hold wait rid].map {|a| (node[a] || '').strip }
  version = node.attribute_with_ns('version', NAMESPACES[:bosh]).value rescue nil

  @session.inactivity = 20
  @session.domain = domain
  @session.content_type = type unless type.empty?
  @session.hold = hold.to_i unless hold.empty?
  @session.wait = wait.to_i unless wait.empty?

  raise StreamErrors::UndefinedCondition.new('rid required') if rid.empty?
  raise StreamErrors::UnsupportedVersion unless version == '1.0'
  raise StreamErrors::ImproperAddressing unless valid_address?(domain)
  raise StreamErrors::HostUnknown unless config.vhost?(domain)
  raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:http_bind]

  Sessions[@session.id] = @session
  send_stream_header
end
stream_write(data)

Alias the Stream#write method before overriding it so we can call it later from a Session instance.

Alias for: write
terminate() click to toggle source
# File lib/vines/stream/http.rb, line 121
def terminate
  doc = Nokogiri::XML::Document.new
  node = doc.create_element('body',
    'type'  => 'terminate',
    'xmlns' => NAMESPACES[:http_bind])
  @session.reply(node)
  close_stream
end
valid_session?(sid) click to toggle source

If the session ID is valid, switch this stream's session to the new ID and return true. Some clients, like Google Chrome, reuse one stream for multiple sessions.

sid - The String session ID.

Returns true if the server previously distributed this SID to a client.

# File lib/vines/stream/http.rb, line 35
def valid_session?(sid)
  if session = Sessions[sid]
    @session = session
  end
  !!session
end
write(data) click to toggle source

Override Stream#write to queue stanzas rather than immediately writing to the stream. Stanza responses must be paired with a queued request.

If a request is not waiting, the written stanzas will buffer until they can be sent in the next response.

data - The XML String or XML::Node to write to the HTTP socket.

Returns nothing.

# File lib/vines/stream/http.rb, line 80
def write(data)
  @session.write(data)
end
Also aliased as: stream_write

Private Instance Methods

close_stream() click to toggle source

Override Stream#close_stream to simply close the connection without writing a closing stream tag.

Returns nothing.

# File lib/vines/stream/http.rb, line 179
def close_stream
  close_connection_after_writing
  @session.close
end
send_stream_error(e) click to toggle source

Override Stream#send_stream_error to wrap the error XML in a BOSH terminate body tag.

e - The StreamError that caused the connection to close.

Returns nothing.

# File lib/vines/stream/http.rb, line 164
def send_stream_error(e)
  doc = Nokogiri::XML::Document.new
  node = doc.create_element('body',
    'condition'    => 'remote-stream-error',
    'type'         => 'terminate',
    'xmlns'        => NAMESPACES[:http_bind],
    'xmlns:stream' => NAMESPACES[:stream])
  node.inner_html = e.to_xml
  @session.reply(node)
end
send_stream_header() click to toggle source
# File lib/vines/stream/http.rb, line 132
def send_stream_header
  doc = Nokogiri::XML::Document.new
  node = doc.create_element('body',
    'charsets'     => 'UTF-8',
    'from'         => @session.domain,
    'hold'         => @session.hold,
    'inactivity'   => @session.inactivity,
    'polling'      => '5',
    'requests'     => '2',
    'sid'          => @session.id,
    'ver'          => '1.6',
    'wait'         => @session.wait,
    'xmpp:version' => '1.0',
    'xmlns'        => NAMESPACES[:http_bind],
    'xmlns:xmpp'   => NAMESPACES[:bosh],
    'xmlns:stream' => NAMESPACES[:stream])

  node << doc.create_element('stream:features') do |el|
    el << doc.create_element('mechanisms') do |mechanisms|
      mechanisms.default_namespace = NAMESPACES[:sasl]
      mechanisms << doc.create_element('mechanism', 'PLAIN')
    end
  end
  @session.reply(node)
end