julien51-ruby_bosh 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Pradeep Elankumaran
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,37 @@
1
+ ruby_bosh
2
+ =========
3
+
4
+ The RubyBOSH library handles creating and pre-authenticating BOSH streams inside your Ruby application before passing them off to your template engine.
5
+
6
+ This method allows you to hide authentication details for your users' XMPP accounts.
7
+
8
+ Tested on Rails 2.x with eJabberd 1.2+
9
+
10
+ References
11
+ ==========
12
+ BOSH: http://xmpp.org/extensions/xep-0124.html
13
+ XMPP via BOSH: http://xmpp.org/extensions/xep-0206.html
14
+
15
+ Example
16
+ =======
17
+ In your Ruby app controller (or equivalent):
18
+
19
+ @session_jid, @session_id, @session_random_id =
20
+ RubyBOSH.initialize_session("me@jabber.org", "my_password", "http://localhost:5280/http-bind")
21
+
22
+ In your template, you would then pass these directly to your javascript BOSH connector:
23
+
24
+ var bosh_jid = '<%= @session_jid %>';
25
+ var bosh_sid = '<%= @session_id %>';
26
+ var bosh_rid = '<%= @session_random_id %>';
27
+
28
+ // using Strophe:
29
+ connect.attach(bosh_jid, bosh_sid, bosh_rid, onConnectHandlerFunction);
30
+
31
+ Acknowledgements
32
+ ================
33
+ Jack Moffit
34
+ - thanks for the nice Django example :)
35
+ #=> http://metajack.im/2008/10/03/getting-attached-to-strophe/
36
+
37
+ Copyright (c) 2008 Pradeep Elankumaran. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "ruby_bosh"
7
+ s.summary = %Q{A BOSH session pre-initializer for Ruby web applications}
8
+ s.email = "pradeep@intridea.com"
9
+ s.homepage = "http://github.com/skyfallsin/ruby_bosh"
10
+ s.description = "TODO"
11
+ s.authors = ["Pradeep Elankumaran"]
12
+
13
+ s.add_dependency("builder")
14
+ s.add_dependency("rest-client")
15
+ s.add_dependency("hpricot")
16
+ s.add_dependency("SystemTimer")
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
22
+ require 'rake/rdoctask'
23
+ Rake::RDocTask.new do |rdoc|
24
+ rdoc.rdoc_dir = 'rdoc'
25
+ rdoc.title = 'ruby_bosh'
26
+ rdoc.options << '--line-numbers' << '--inline-source'
27
+ rdoc.rdoc_files.include('README*')
28
+ rdoc.rdoc_files.include('lib/**/*.rb')
29
+ end
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib' << 'test'
34
+ t.pattern = 'test/**/*_test.rb'
35
+ t.verbose = false
36
+ end
37
+
38
+ begin
39
+ require 'rcov/rcovtask'
40
+ Rcov::RcovTask.new do |t|
41
+ t.libs << 'test'
42
+ t.test_files = FileList['test/**/*_test.rb']
43
+ t.verbose = true
44
+ end
45
+ rescue LoadError
46
+ puts "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
47
+ end
48
+
49
+ begin
50
+ require 'cucumber/rake/task'
51
+ Cucumber::Rake::Task.new(:features)
52
+ rescue LoadError
53
+ puts "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
54
+ end
55
+
56
+ task :default => :test
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 5
3
+ :major: 0
4
+ :minor: 5
data/lib/ruby_bosh.rb ADDED
@@ -0,0 +1,169 @@
1
+ require 'rest_client'
2
+ require 'builder'
3
+ require 'rexml/document'
4
+ require 'base64'
5
+ require 'hpricot'
6
+ require 'timeout'
7
+ require 'system_timer'
8
+
9
+ class RubyBOSH
10
+ BOSH_XMLNS = 'http://jabber.org/protocol/httpbind'
11
+ TLS_XMLNS = 'urn:ietf:params:xml:ns:xmpp-tls'
12
+ SASL_XMLNS = 'urn:ietf:params:xml:ns:xmpp-sasl'
13
+ BIND_XMLNS = 'urn:ietf:params:xml:ns:xmpp-bind'
14
+ SESSION_XMLNS = 'urn:ietf:params:xml:ns:xmpp-session'
15
+ CLIENT_XMLNS = 'jabber:client'
16
+
17
+ class Error < StandardError; end
18
+ class Timeout < RubyBOSH::Error; end
19
+ class AuthFailed < RubyBOSH::Error; end
20
+ class ConnFailed < RubyBOSH::Error; end
21
+
22
+ @@logging = true
23
+
24
+ def self.logging=(value)
25
+ @@logging = value
26
+ end
27
+
28
+ attr_accessor :jid, :rid, :sid, :success
29
+
30
+ def initialize(jid, pw, service_url, opts={})
31
+ @service_url = service_url
32
+ @jid, @pw = jid, pw
33
+ @host = jid.split("@").last
34
+ @success = false
35
+ @timeout = opts[:timeout] || 30 #seconds
36
+ @headers = {"Content-Type" => "text/xml; charset=utf-8",
37
+ "Accept" => "text/xml"}
38
+ @wait = opts[:wait] || 60
39
+ @hold = opts[:hold] || 1
40
+ @window = opts[:window] || 10
41
+ end
42
+
43
+ def success?
44
+ @success == true
45
+ end
46
+
47
+ def self.initialize_session(*args)
48
+ new(*args).connect
49
+ end
50
+
51
+ def connect
52
+ initialize_bosh_session
53
+ if send_auth_request
54
+ send_restart_request
55
+ request_resource_binding
56
+ @success = send_session_request
57
+ end
58
+
59
+ raise RubyBOSH::AuthFailed, "could not authenticate #{@jid}" unless success?
60
+
61
+ self
62
+ end
63
+
64
+ def send_xml(&block)
65
+ request = construct_body(:sid => @sid, &block)
66
+ response = deliver(request)
67
+ end
68
+
69
+ private
70
+
71
+ def initialize_bosh_session
72
+ response = deliver(construct_body(:wait => @wait, :to => @host,
73
+ :hold => @hold, :window => @window,
74
+ "xmpp:version" => '1.0'))
75
+ parse(response)
76
+ end
77
+
78
+ def construct_body(params = {}, &block)
79
+ @rid ? @rid += 1 : @rid = rand(100000)
80
+
81
+ builder = Builder::XmlMarkup.new
82
+ parameters = {:rid => @rid, :xmlns => BOSH_XMLNS,
83
+ "xmpp:version" => "1.0",
84
+ "xmlns:xmpp" => "urn:xmpp:xbosh"}.merge(params)
85
+
86
+ if block_given?
87
+ builder.body(parameters) {|body| yield(body)}
88
+ else
89
+ builder.body(parameters)
90
+ end
91
+ end
92
+
93
+ def send_auth_request
94
+ request = construct_body(:sid => @sid) do |body|
95
+ auth_string = "#{@jid}\x00#{@jid.split("@").first.strip}\x00#{@pw}"
96
+ body.auth(Base64.encode64(auth_string).gsub(/\s/,''),
97
+ :xmlns => SASL_XMLNS, :mechanism => 'PLAIN')
98
+ end
99
+
100
+ response = deliver(request)
101
+ response.include?("success")
102
+ end
103
+
104
+ def send_restart_request
105
+ request = construct_body(:sid => @sid, "xmpp:restart" => true, "xmlns:xmpp" => 'urn:xmpp:xbosh')
106
+ deliver(request).include?("stream:features")
107
+ end
108
+
109
+ def request_resource_binding
110
+ request = construct_body(:sid => @sid) do |body|
111
+ body.iq(:id => "bind_#{rand(100000)}", :type => "set",
112
+ :xmlns => "jabber:client") do |iq|
113
+ iq.bind(:xmlns => BIND_XMLNS) do |bind|
114
+ bind.resource("bosh_#{rand(10000)}")
115
+ end
116
+ end
117
+ end
118
+
119
+ response = deliver(request)
120
+ response.include?("<jid>")
121
+ end
122
+
123
+ def send_session_request
124
+ request = construct_body(:sid => @sid) do |body|
125
+ body.iq(:xmlns => CLIENT_XMLNS, :type => "set",
126
+ :id => "sess_#{rand(100000)}") do |iq|
127
+ iq.session(:xmlns => SESSION_XMLNS)
128
+ end
129
+ end
130
+
131
+ response = deliver(request)
132
+ response.include?("body")
133
+ end
134
+
135
+ def parse(_response)
136
+ doc = Hpricot(_response)
137
+ doc.search("//body").each do |body|
138
+ @sid = body.attributes["sid"].to_s
139
+ end
140
+ _response
141
+ end
142
+
143
+ def deliver(xml)
144
+ SystemTimer.timeout(@timeout) do
145
+ send(xml)
146
+ recv(RestClient.post(@service_url, xml, @headers))
147
+ end
148
+ rescue ::Timeout::Error => e
149
+ raise RubyBOSH::Timeout, e.message
150
+ rescue Errno::ECONNREFUSED => e
151
+ raise RubyBOSH::ConnFailed, "could not connect to #{@host}\n#{e.message}"
152
+ rescue Exception => e
153
+ raise RubyBOSH::Error, e.message
154
+ end
155
+
156
+ def send(msg)
157
+ puts("Ruby-BOSH - SEND\n#{msg}") if @@logging; msg
158
+ end
159
+
160
+ def recv(msg)
161
+ puts("Ruby-BOSH - RECV\n#{msg}") if @@logging; msg
162
+ end
163
+ end
164
+
165
+
166
+ if __FILE__ == $0
167
+ p RubyBOSH.initialize_session(ARGV[0], ARGV[1],
168
+ "http://localhost:5280/http-bind")
169
+ end
@@ -0,0 +1,63 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe RubyBOSH do
4
+ before(:each) do
5
+ RubyBOSH.logging = false
6
+ @rbosh = RubyBOSH.new("skyfallsin@localhost", "skyfallsin",
7
+ "http://localhost:5280/http-bind")
8
+ #@rbosh.stub!(:success?).and_return(true)
9
+ #@rbosh.stub!(:initialize_bosh_session).and_return(true)
10
+ @rbosh.stub!(:send_auth_request).and_return(true)
11
+ @rbosh.stub!(:send_restart_request).and_return(true)
12
+ @rbosh.stub!(:request_resource_binding).and_return(true)
13
+ @rbosh.stub!(:send_session_request).and_return(true)
14
+ RestClient.stub!(:post).and_return("<body sid='123456'></body>")
15
+ end
16
+
17
+ it "should set the sid attribute after the session creation request" do
18
+ @rbosh.connect
19
+ @rbosh.sid.should == '123456'
20
+ end
21
+
22
+ it "should update the rid on every call to the BOSH server" do
23
+ @rbosh.rid = 100
24
+ @rbosh.connect
25
+ @rbosh.rid.should > 100
26
+ end
27
+
28
+ it "should return an array with [jid, sid, rid] on success" do
29
+ s = @rbosh.connect
30
+ s.should be_kind_of(RubyBOSH)
31
+ s.jid.should == 'skyfallsin@localhost'
32
+ s.rid.should be_kind_of(Fixnum)
33
+ s.sid.should == '123456'
34
+ end
35
+
36
+ describe "Errors" do
37
+ it "should crash with AuthFailed when its not a success?" do
38
+ @rbosh.stub!(:send_session_request).and_return(false)
39
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::AuthFailed)
40
+ end
41
+
42
+ it "should raise a ConnFailed if a connection could not be made to the XMPP server" do
43
+ RestClient.stub!(:post).and_raise(Errno::ECONNREFUSED)
44
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::ConnFailed)
45
+ end
46
+
47
+ it "should raise a Timeout::Error if the BOSH call takes forever" do
48
+ SystemTimer.stub!(:timeout).and_raise(::Timeout::Error)
49
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::Timeout)
50
+ end
51
+
52
+ it "should crash with a generic error on any other problem" do
53
+ [RestClient::ServerBrokeConnection, RestClient::RequestTimeout].each{|err|
54
+ RestClient.stub!(:post).and_raise(err)
55
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::Error)
56
+ }
57
+ end
58
+
59
+ after(:each) do
60
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::Error)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), '..', "lib", "ruby_bosh")
3
+ require 'spec'
4
+
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: julien51-ruby_bosh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.5
5
+ platform: ruby
6
+ authors:
7
+ - Pradeep Elankumaran
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-03 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: builder
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rest-client
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: hpricot
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: SystemTimer
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ description: TODO
56
+ email: pradeep@intridea.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - LICENSE
63
+ - README
64
+ files:
65
+ - LICENSE
66
+ - Rakefile
67
+ - VERSION.yml
68
+ - lib/ruby_bosh.rb
69
+ - spec/ruby_bosh_spec.rb
70
+ - spec/spec_helper.rb
71
+ - README
72
+ has_rdoc: true
73
+ homepage: http://github.com/skyfallsin/ruby_bosh
74
+ licenses:
75
+ post_install_message:
76
+ rdoc_options:
77
+ - --charset=UTF-8
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: "0"
85
+ version:
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: "0"
91
+ version:
92
+ requirements: []
93
+
94
+ rubyforge_project:
95
+ rubygems_version: 1.3.5
96
+ signing_key:
97
+ specification_version: 2
98
+ summary: A BOSH session pre-initializer for Ruby web applications
99
+ test_files:
100
+ - spec/ruby_bosh_spec.rb
101
+ - spec/spec_helper.rb