ruby_bosh.rb
4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
require 'rest_client'
require 'builder'
require 'rexml/document'
require 'base64'
require 'nokogiri'
require 'timeout'
class RubyBOSH
BOSH_XMLNS = 'http://jabber.org/protocol/httpbind'
TLS_XMLNS = 'urn:ietf:params:xml:ns:xmpp-tls'
SASL_XMLNS = 'urn:ietf:params:xml:ns:xmpp-sasl'
BIND_XMLNS = 'urn:ietf:params:xml:ns:xmpp-bind'
SESSION_XMLNS = 'urn:ietf:params:xml:ns:xmpp-session'
CLIENT_XMLNS = 'jabber:client'
class Error < StandardError; end
class TimeoutError < RubyBOSH::Error; end
class AuthFailed < RubyBOSH::Error; end
class ConnFailed < RubyBOSH::Error; end
@@logging = true
def self.logging=(value)
@@logging = value
end
attr_accessor :jid, :rid, :sid, :success
def initialize(jid, pw, service_url, opts={})
@service_url = service_url
@jid, @pw = jid, pw
@host = jid.split("@").last
@success = false
@timeout = opts[:timeout] || 3 #seconds
@headers = {"Content-Type" => "text/xml; charset=utf-8",
"Accept" => "text/xml"}
@wait = opts[:wait] || 5
@hold = opts[:hold] || 3
@window = opts[:window] || 5
end
def success?
@success == true
end
def self.initialize_session(*args)
new(*args).connect
end
def connect
initialize_bosh_session
if send_auth_request
send_restart_request
request_resource_binding
@success = send_session_request
end
raise RubyBOSH::AuthFailed, "could not authenticate #{@jid}" unless success?
@rid += 1 #updates the rid for the next call from the browser
[@jid, @sid, @rid]
end
private
def initialize_bosh_session
response = deliver(construct_body(:wait => @wait, :to => @host,
:hold => @hold, :window => @window,
"xmpp:version" => '1.0'))
parse(response)
end
def construct_body(params={}, &block)
@rid ? @rid+=1 : @rid=rand(100000)
builder = Builder::XmlMarkup.new
parameters = {:rid => @rid, :xmlns => BOSH_XMLNS,
"xmpp:version" => "1.0",
"xmlns:xmpp" => "urn:xmpp:xbosh"}.merge(params)
if block_given?
builder.body(parameters) {|body| yield(body)}
else
builder.body(parameters)
end
end
def send_auth_request
request = construct_body(:sid => @sid) do |body|
auth_string = "#{@jid}\x00#{@jid.split("@").first.strip}\x00#{@pw}"
body.auth(Base64.encode64(auth_string).gsub(/\s/,''),
:xmlns => SASL_XMLNS, :mechanism => 'PLAIN')
end
response = deliver(request)
response.include?("success")
end
def send_restart_request
request = construct_body(:sid => @sid, "xmpp:restart" => true, "xmlns:xmpp" => 'urn:xmpp:xbosh')
deliver(request).include?("stream:features")
end
def request_resource_binding
request = construct_body(:sid => @sid) do |body|
body.iq(:id => "bind_#{rand(100000)}", :type => "set",
:xmlns => "jabber:client") do |iq|
iq.bind(:xmlns => BIND_XMLNS) do |bind|
bind.resource("bosh_#{rand(10000)}")
end
end
end
response = deliver(request)
response.include?("<jid>")
end
def send_session_request
request = construct_body(:sid => @sid) do |body|
body.iq(:xmlns => CLIENT_XMLNS, :type => "set",
:id => "sess_#{rand(100000)}") do |iq|
iq.session(:xmlns => SESSION_XMLNS)
end
end
response = deliver(request)
response.include?("body")
end
def parse(_response)
doc = Nokogiri::HTML.fragment(_response.to_s)
doc.search("body").each do |body|
@sid = body.attributes["sid"].to_s
end
_response
end
def deliver(xml)
Timeout::timeout(@timeout) do
send(xml)
recv(RestClient.post(@service_url, xml, @headers))
end
rescue ::Timeout::Error => e
raise RubyBOSH::TimeoutError, e.message
rescue Errno::ECONNREFUSED => e
raise RubyBOSH::ConnFailed, "could not connect to #{@host}\n#{e.message}"
rescue Exception => e
raise RubyBOSH::Error, e.message
end
def send(msg)
puts("Ruby-BOSH - SEND\n#{msg}") if @@logging; msg
end
def recv(msg)
puts("Ruby-BOSH - RECV\n#{msg}") if @logging; msg
end
end
if __FILE__ == $0
p RubyBOSH.initialize_session(ARGV[0], ARGV[1],
"http://localhost:5280/http-bind")
end