julien51-babylon 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +14 -5
- data/lib/babylon.rb +9 -8
- data/lib/babylon/client_connection.rb +147 -0
- data/lib/babylon/component_connection.rb +24 -8
- data/lib/babylon/runner.rb +9 -2
- data/lib/babylon/xmpp_connection.rb +15 -21
- data/templates/babylon/config/config.yaml +1 -0
- metadata +2 -1
data/README.rdoc
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
== DESCRIPTION:
|
4
4
|
|
5
|
-
Babylon is a framework to
|
5
|
+
Babylon is a framework to build XMPP Applications in Ruby. The framework uses EventMachine to handle network connections.
|
6
|
+
|
7
|
+
This framework can use both an XMPP Component (XEP-0114) and an XMPP Client. However, we strongly discourage any production application using a regular client.
|
6
8
|
|
7
9
|
== FEATURES/PROBLEMS:
|
8
10
|
|
@@ -11,11 +13,17 @@ Babylon is a framework to EventMachine based XMPP External Components in Ruby.
|
|
11
13
|
|
12
14
|
== ROADMAP :
|
13
15
|
|
14
|
-
- Instead of passing the stanzas as Nokogiri:XML:Node elements we should find a way to pass an "agnostic"
|
15
|
-
-
|
16
|
+
- Instead of passing the stanzas as Nokogiri:XML:Node elements we should find a way to pass an "agnostic" ruby data-structure, so that app developers don't have to learn/know/understand Nokogiri
|
17
|
+
- HELPERS :
|
18
|
+
* strip jids (...)
|
19
|
+
- Improve the Client Connection to support other authentication than PLAIN SASL
|
16
20
|
|
17
21
|
== SYNOPSIS:
|
18
22
|
|
23
|
+
You can build applications directly with Babylon, or you can use the Babylon::ClientConnection and Babylon::ComponentConnection to create simple apps, but you will then have to handle stanza routing and creation yourself. You can also use these classes in external gems.
|
24
|
+
|
25
|
+
To create an Application with Babylon:
|
26
|
+
|
19
27
|
1. Install the gem
|
20
28
|
2. The app contains a generator that will "build" a scaffold for your application.
|
21
29
|
|
@@ -82,8 +90,9 @@ This code hasn't been tested at all! (yes, i know it's bad, but I couldn't have
|
|
82
90
|
|
83
91
|
== REQUIREMENTS:
|
84
92
|
|
85
|
-
|
86
|
-
|
93
|
+
Gems : Eventmachine, nokogiri, YAML
|
94
|
+
|
95
|
+
Our goal is to limit the number of dependencies. Nokogiri seems to be only XML Library on Ruby that has a Push SAX Parser, that is why we are using it. It also seems pretty 'fast'. However, we think it is bad to "force" the framework users to receive Nokogiri objects (too much coupling), so we're looking for a Ruby datastcuture that would have an easy mapping with XML. Any thoughts?
|
87
96
|
|
88
97
|
== INSTALL:
|
89
98
|
|
data/lib/babylon.rb
CHANGED
@@ -4,12 +4,13 @@ $:.unshift(File.dirname(__FILE__)) unless
|
|
4
4
|
require 'eventmachine'
|
5
5
|
require 'nokogiri'
|
6
6
|
|
7
|
-
require 'babylon/xmpp_connection'
|
8
|
-
require 'babylon/component_connection'
|
9
|
-
require 'babylon/
|
10
|
-
require 'babylon/
|
11
|
-
require 'babylon/
|
12
|
-
require 'babylon/base/
|
7
|
+
require 'babylon/xmpp_connection.rb'
|
8
|
+
require 'babylon/component_connection.rb'
|
9
|
+
require 'babylon/client_connection.rb'
|
10
|
+
require 'babylon/router.rb'
|
11
|
+
require 'babylon/runner.rb'
|
12
|
+
require 'babylon/base/controller.rb'
|
13
|
+
require 'babylon/base/view.rb'
|
13
14
|
|
14
15
|
# Babylon is a XMPP Component Framework based on EventMachine. It uses the Nokogiri GEM, which is a Ruby wrapper for Libxml2.
|
15
16
|
# It implements the MVC paradigm.
|
@@ -18,7 +19,7 @@ require 'babylon/base/view'
|
|
18
19
|
# This will generate some folders and files for your application. Please see README for further instructions
|
19
20
|
|
20
21
|
module Babylon
|
21
|
-
# 0.0.
|
22
|
-
VERSION = '0.0.
|
22
|
+
# 0.0.5 : Not suited for production, use at your own risks
|
23
|
+
VERSION = '0.0.5'
|
23
24
|
end
|
24
25
|
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Babylon
|
2
|
+
|
3
|
+
##
|
4
|
+
# ClientConnection is in charge of the XMPP connection for a Regular XMPP Client.
|
5
|
+
# So far, SASL Plain authenticationonly is supported
|
6
|
+
# Upon stanza reception, and depending on the status (connected... etc), this component will handle or forward the stanzas.
|
7
|
+
class ClientConnection < XmppConnection
|
8
|
+
require 'digest/sha1'
|
9
|
+
require 'base64'
|
10
|
+
|
11
|
+
attr_reader :binding_iq_id, :session_iq_id
|
12
|
+
|
13
|
+
##
|
14
|
+
# Returns true only if we're in connected state
|
15
|
+
def connected?
|
16
|
+
@state == :connected
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Creates a new ClientConnection and waits for data in the stream
|
21
|
+
def initialize(params)
|
22
|
+
super(params)
|
23
|
+
@state = :wait_for_stream
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream
|
28
|
+
# to establish the XMPP connection itself.
|
29
|
+
# We use a "tweak" here to send only the starting tag of stream:stream
|
30
|
+
def connection_completed
|
31
|
+
super
|
32
|
+
builder = Nokogiri::XML::Builder.new do
|
33
|
+
self.send('stream:stream', {'xmlns' => @context.stream_namespace(), 'xmlns:stream' => 'http://etherx.jabber.org/streams', 'to' => @context.config['host'], 'version' => '1.0'}) do
|
34
|
+
paste_content_here # The stream:stream element should be cut here ;)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@outstream = builder.doc
|
38
|
+
start_stream, stop_stream = builder.to_xml.split('<paste_content_here/>')
|
39
|
+
send_data(start_stream)
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Called upon stanza reception
|
44
|
+
# Marked as connected when the client has been SASLed, authenticated, biund to a resource and when the session has been created
|
45
|
+
def receive_stanza(stanza)
|
46
|
+
case @state
|
47
|
+
when :connected
|
48
|
+
super # Can be dispatched
|
49
|
+
|
50
|
+
when :wait_for_stream
|
51
|
+
if stanza.name == "stream:stream" && stanza.attributes['id']
|
52
|
+
@state = :wait_for_auth_mechanisms unless @success
|
53
|
+
@state = :wait_for_bind if @success
|
54
|
+
end
|
55
|
+
|
56
|
+
when :wait_for_auth_mechanisms
|
57
|
+
if stanza.name == "stream:features"
|
58
|
+
if stanza.at("starttls") # we shall start tls
|
59
|
+
starttls = Nokogiri::XML::Node.new("starttls", @outstream)
|
60
|
+
starttls["xmlns"] = stanza.at("starttls").namespaces.first.last
|
61
|
+
send(starttls)
|
62
|
+
@state = :wait_for_proceed
|
63
|
+
elsif stanza.at("mechanisms") # tls is ok
|
64
|
+
if stanza.at("mechanisms/[contains(mechanism,'PLAIN')]")
|
65
|
+
# auth_text = "#{jid.strip}\x00#{jid.node}\x00#{password}"
|
66
|
+
auth = Nokogiri::XML::Node.new("auth", @outstream)
|
67
|
+
auth['mechanism'] = "PLAIN"
|
68
|
+
auth['xmlns'] = stanza.at("mechanisms").namespaces.first.last
|
69
|
+
auth.content = Base64::encode64([@config['jid'],@config['jid'].split("@").first,@config['password']].join("\000")).gsub(/\s/, '')
|
70
|
+
send(auth)
|
71
|
+
@state = :wait_for_success
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
when :wait_for_success
|
77
|
+
if stanza.name == "success" # Yay! Success
|
78
|
+
@success = true
|
79
|
+
@state = :wait_for_stream
|
80
|
+
send_data @outstream.root.to_xml.split('<paste_content_here/>').first
|
81
|
+
else
|
82
|
+
# Hum Failure...
|
83
|
+
end
|
84
|
+
|
85
|
+
when :wait_for_bind
|
86
|
+
if stanza.name == "stream:features"
|
87
|
+
if stanza.at("bind")
|
88
|
+
# Let's build the binding_iq
|
89
|
+
@binding_iq_id = Integer(rand(10000))
|
90
|
+
builder = Nokogiri::XML::Builder.new do
|
91
|
+
iq(:type => "set", :id => @context.binding_iq_id) do
|
92
|
+
bind(:xmlns => "urn:ietf:params:xml:ns:xmpp-bind") do
|
93
|
+
if @context.config['resource']
|
94
|
+
resource(@context.config['resource'] )
|
95
|
+
else
|
96
|
+
resource("babylon_client_#{@context.binding_iq_id}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
iq = @outstream.add_child(builder.doc.root)
|
102
|
+
send(iq)
|
103
|
+
@state = :wait_for_confirmed_binding
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
when :wait_for_confirmed_binding
|
108
|
+
if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == @binding_iq_id
|
109
|
+
if stanza.at("jid")
|
110
|
+
@jid = stanza.at("jid").text
|
111
|
+
end
|
112
|
+
end
|
113
|
+
# And now, we must initiate the session
|
114
|
+
@session_iq_id = Integer(rand(10000))
|
115
|
+
builder = Nokogiri::XML::Builder.new do
|
116
|
+
iq(:type => "set", :id => @context.session_iq_id) do
|
117
|
+
session(:xmlns => "urn:ietf:params:xml:ns:xmpp-session")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
iq = @outstream.add_child(builder.doc.root)
|
121
|
+
send(iq)
|
122
|
+
@state = :wait_for_confirmed_session
|
123
|
+
|
124
|
+
when :wait_for_confirmed_session
|
125
|
+
if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == @session_iq_id && stanza.at("session")
|
126
|
+
# And now, send a presence!
|
127
|
+
presence = Nokogiri::XML::Node.new("presence", @outstream)
|
128
|
+
send(presence)
|
129
|
+
@state = :connected
|
130
|
+
end
|
131
|
+
|
132
|
+
when :wait_for_proceed
|
133
|
+
start_tls() # starting TLS
|
134
|
+
@state = :wait_for_stream
|
135
|
+
send_data @outstream.root.to_xml.split('<paste_content_here/>').first
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Namespace of the client
|
142
|
+
def stream_namespace
|
143
|
+
"jabber:client"
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
@@ -5,19 +5,42 @@ module Babylon
|
|
5
5
|
class ComponentConnection < XmppConnection
|
6
6
|
require 'digest/sha1'
|
7
7
|
|
8
|
+
##
|
9
|
+
# Returns true only if we're in connected state
|
10
|
+
def connected?
|
11
|
+
@state == :connected
|
12
|
+
end
|
13
|
+
|
8
14
|
##
|
9
15
|
# Creates a new ComponentConnection and waits for data in the stream
|
10
16
|
def initialize(*a)
|
11
17
|
super
|
12
18
|
@state = :wait_for_stream
|
13
19
|
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream
|
23
|
+
# to establish the XMPP connection itself.
|
24
|
+
# We use a "tweak" here to send only the starting tag of stream:stream
|
25
|
+
def connection_completed
|
26
|
+
super
|
27
|
+
builder = Nokogiri::XML::Builder.new do
|
28
|
+
self.send('stream:stream', {'xmlns' => "jabber:component:accept", 'xmlns:stream' => 'http://etherx.jabber.org/streams', 'to' => @context.config['jid']}) do
|
29
|
+
paste_content_here # The stream:stream element should be cut here ;)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@start_stream, @stop_stream = builder.to_xml.split('<paste_content_here/>')
|
33
|
+
send_data(@start_stream)
|
34
|
+
end
|
14
35
|
|
15
36
|
##
|
16
37
|
# XMPP Component handshake as defined in XEP-0114:
|
17
38
|
# http://xmpp.org/extensions/xep-0114.html
|
18
39
|
def receive_stanza(stanza)
|
19
40
|
case @state
|
20
|
-
|
41
|
+
when :connected # Most frequent case
|
42
|
+
super # Can be dispatched
|
43
|
+
|
21
44
|
when :wait_for_stream
|
22
45
|
if stanza.name == "stream:stream" && stanza.attributes['id']
|
23
46
|
# This means the XMPP session started!
|
@@ -40,8 +63,6 @@ module Babylon
|
|
40
63
|
raise
|
41
64
|
end
|
42
65
|
|
43
|
-
when :connected
|
44
|
-
super # Can be dispatched
|
45
66
|
end
|
46
67
|
end
|
47
68
|
|
@@ -51,10 +72,5 @@ module Babylon
|
|
51
72
|
'jabber:component:accept'
|
52
73
|
end
|
53
74
|
|
54
|
-
##
|
55
|
-
# Jid of the component
|
56
|
-
def stream_to
|
57
|
-
@config['jid']
|
58
|
-
end
|
59
75
|
end
|
60
76
|
end
|
data/lib/babylon/runner.rb
CHANGED
@@ -24,8 +24,15 @@ module Babylon
|
|
24
24
|
# Starting the EventMachine
|
25
25
|
EventMachine.epoll
|
26
26
|
EventMachine::run do
|
27
|
-
|
28
|
-
|
27
|
+
if config["application_type"] == "client"
|
28
|
+
Babylon::ClientConnection.connect(config) do |stanza|
|
29
|
+
CentralRouter.route stanza # Upon reception of new stanza, we Route them through the controller
|
30
|
+
end
|
31
|
+
else
|
32
|
+
Babylon::ComponentConnection.connect(config) do |stanza|
|
33
|
+
CentralRouter.route stanza # Upon reception of new stanza, we Route them through the controller
|
34
|
+
end
|
35
|
+
end
|
29
36
|
# And finally, let's allow the application to do all it wants to do after we started the EventMachine!
|
30
37
|
callback.call if callback
|
31
38
|
end
|
@@ -13,8 +13,8 @@ module Babylon
|
|
13
13
|
##
|
14
14
|
# Connects the XmppConnection to the right host with the right port.
|
15
15
|
# It passes itself (as handler) and the configuration
|
16
|
-
def self.connect(config)
|
17
|
-
EventMachine::connect config['host'], config['port'], self, config
|
16
|
+
def self.connect(config, &block)
|
17
|
+
EventMachine::connect config['host'], config['port'], self, config.merge({:callback => block})
|
18
18
|
end
|
19
19
|
|
20
20
|
##
|
@@ -34,40 +34,32 @@ module Babylon
|
|
34
34
|
##
|
35
35
|
# Called when a full stanza has been received and returns it to the central router to be sent to the corresponding controller. Eventually it displays this data for debugging purposes
|
36
36
|
def receive_stanza(stanza)
|
37
|
-
puts "<< #{stanza}\n" if debug? # Low level Logging
|
38
37
|
# If not handled by subclass (for authentication)
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
##
|
43
|
-
# Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream to establish the XMPP connection itself
|
44
|
-
# We use a "tweak" here to send only the starting tag of stream:stream
|
45
|
-
def connection_completed
|
46
|
-
super
|
47
|
-
builder = Nokogiri::XML::Builder.new {
|
48
|
-
self.send('stream:stream', 'xmlns' => "jabber:component:accept", 'xmlns:stream' => 'http://etherx.jabber.org/streams', 'to' => @context.config['jid']) {
|
49
|
-
paste_content_here # The stream:stream element should be cut here ;)
|
50
|
-
}
|
51
|
-
}
|
52
|
-
@start_stream, @stop_stream = builder.to_xml.split('<paste_content_here/>')
|
53
|
-
send_data(@start_stream)
|
38
|
+
@config[:callback].call(stanza) if @config[:callback]
|
54
39
|
end
|
55
40
|
|
56
41
|
##
|
57
42
|
# Sends the Nokogiri::XML data (after converting to string) on the stream. It also appends the right "from" to be the component's JId if none has been mentionned. Eventually it displays this data for debugging purposes
|
58
43
|
def send(xml)
|
59
44
|
if !xml.attributes["from"]
|
60
|
-
xml["from"] =
|
45
|
+
xml["from"] = jid
|
61
46
|
end
|
62
|
-
puts "
|
47
|
+
puts "SENDING #{xml}\n" if debug? # Very low level Logging
|
63
48
|
send_data "#{xml}"
|
64
49
|
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Memoizer for jid. The jid can actually be changed in subclasses (client will probbaly want to change it to include the resource)
|
53
|
+
def jid
|
54
|
+
@jid ||= config['jid']
|
55
|
+
end
|
65
56
|
|
66
57
|
private
|
67
58
|
|
68
59
|
##
|
69
60
|
# receive_data is called when data is received. It is then passed to the parser.
|
70
61
|
def receive_data(data)
|
62
|
+
puts "RECEIVED #{data}\n" if debug? # Low level Logging
|
71
63
|
@parser.parse data
|
72
64
|
end
|
73
65
|
|
@@ -121,8 +113,10 @@ module Babylon
|
|
121
113
|
# If we don't have any elem yet, we are at the root
|
122
114
|
@elem = @elem ? @elem.add_child(e) : (@root = e)
|
123
115
|
|
124
|
-
if @elem.
|
116
|
+
if @elem.name == "stream:stream"
|
125
117
|
# Should be called only for stream:stream
|
118
|
+
@doc = Nokogiri::XML::Document.new
|
119
|
+
@root = @elem
|
126
120
|
@doc.root = @elem
|
127
121
|
@callback.call(@elem)
|
128
122
|
end
|
@@ -5,6 +5,7 @@
|
|
5
5
|
# host: host on which the XMPP server is running
|
6
6
|
# port: port to which your component should connect
|
7
7
|
# debug: outputs the xmpp stanzas
|
8
|
+
# application_type: client | component (by default it is component and we strongly discourage any client application in production)
|
8
9
|
|
9
10
|
development:
|
10
11
|
jid: component.server.com
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: julien51-babylon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julien Genestoux
|
@@ -47,6 +47,7 @@ files:
|
|
47
47
|
- lib/babylon/base/controller.rb
|
48
48
|
- lib/babylon/base/view.rb
|
49
49
|
- lib/babylon/component_connection.rb
|
50
|
+
- lib/babylon/client_connection.rb
|
50
51
|
- lib/babylon/router.rb
|
51
52
|
- lib/babylon/runner.rb
|
52
53
|
- lib/babylon/xmpp_connection.rb
|