julien51-babylon 0.0.14 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +14 -16
- data/Rakefile +1 -3
- data/lib/babylon.rb +37 -6
- data/lib/babylon/base/controller.rb +32 -24
- data/lib/babylon/base/stanza.rb +2 -4
- data/lib/babylon/base/view.rb +19 -19
- data/lib/babylon/client_connection.rb +93 -80
- data/lib/babylon/component_connection.rb +13 -9
- data/lib/babylon/generator.rb +1 -1
- data/lib/babylon/router.rb +48 -86
- data/lib/babylon/router/dsl.rb +0 -6
- data/lib/babylon/runner.rb +56 -34
- data/lib/babylon/xmpp_connection.rb +56 -46
- data/lib/babylon/xmpp_parser.rb +50 -53
- data/templates/babylon/config/boot.rb +1 -1
- data/templates/babylon/config/config.yaml +1 -3
- data/templates/babylon/config/routes.rb +1 -1
- data/templates/babylon/log/test.log +52 -0
- data/templates/babylon/script/component +36 -20
- metadata +2 -2
@@ -18,12 +18,15 @@ module Babylon
|
|
18
18
|
# We use a "tweak" here to send only the starting tag of stream:stream
|
19
19
|
def connection_completed
|
20
20
|
super
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
doc = Nokogiri::XML::Document.new
|
22
|
+
stream = Nokogiri::XML::Node.new("stream", doc)
|
23
|
+
stream.add_namespace(nil, stream_namespace)
|
24
|
+
stream.add_namespace("stream", "http://etherx.jabber.org/streams")
|
25
|
+
stream["to"] = jid
|
26
|
+
doc.add_child(stream)
|
27
|
+
paste_content_here= Nokogiri::XML::Node.new("paste_content_here", doc)
|
28
|
+
stream.add_child(paste_content_here)
|
29
|
+
start, stop = doc.to_xml.split('<stream:paste_content_here/>')
|
27
30
|
send_xml(start)
|
28
31
|
end
|
29
32
|
|
@@ -33,7 +36,7 @@ module Babylon
|
|
33
36
|
def receive_stanza(stanza)
|
34
37
|
case @state
|
35
38
|
when :connected # Most frequent case
|
36
|
-
super # Can be dispatched
|
39
|
+
super(stanza) # Can be dispatched
|
37
40
|
|
38
41
|
when :wait_for_stream
|
39
42
|
if stanza.name == "stream:stream" && stanza.attributes['id']
|
@@ -72,11 +75,12 @@ module Babylon
|
|
72
75
|
|
73
76
|
def handshake(stanza)
|
74
77
|
hash = Digest::SHA1::hexdigest(stanza.attributes['id'].content + @password)
|
75
|
-
|
78
|
+
doc = Nokogiri::XML::Document.new
|
79
|
+
handshake = Nokogiri::XML::Node.new("handshake", doc)
|
80
|
+
doc.add_child(handshake)
|
76
81
|
handshake.content = hash
|
77
82
|
handshake
|
78
83
|
end
|
79
84
|
|
80
|
-
|
81
85
|
end
|
82
86
|
end
|
data/lib/babylon/generator.rb
CHANGED
@@ -99,7 +99,7 @@ module Babylon
|
|
99
99
|
##
|
100
100
|
# This is a hack since Templater doesn't offer any simple way to edit files right now...
|
101
101
|
def add_route_for_actions_in_controller(actions, controller)
|
102
|
-
sentinel = "Babylon
|
102
|
+
sentinel = "Babylon.router.draw do"
|
103
103
|
router_path = "config/routes.rb"
|
104
104
|
actions.each do |action|
|
105
105
|
to_inject = "xpath(\"#{action[2]}\").to(:controller => \"#{controller}\", :action => \"#{action[0]}\").priority(#{action[1]})"
|
data/lib/babylon/router.rb
CHANGED
@@ -1,108 +1,74 @@
|
|
1
1
|
require File.dirname(__FILE__)+"/router/dsl"
|
2
2
|
|
3
3
|
module Babylon
|
4
|
-
|
5
|
-
|
6
|
-
##
|
7
|
-
# Undefined stanza
|
8
|
-
class UndefinedStanza < Exception; end
|
9
|
-
|
10
4
|
##
|
11
|
-
#
|
12
|
-
|
5
|
+
# Routers are in charge of sending the right stanzas to the right controllers based on user defined Routes.
|
6
|
+
# Each application can have only one!
|
7
|
+
class StanzaRouter
|
13
8
|
|
14
|
-
|
9
|
+
attr_reader :routes, :connection
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
# Routes should be of form {name => params}
|
19
|
-
def add_routes(routes)
|
20
|
-
routes.each do |name, params|
|
21
|
-
add_route(Route.new(params))
|
22
|
-
end
|
11
|
+
def initialize
|
12
|
+
@routes = []
|
23
13
|
end
|
24
14
|
|
25
15
|
##
|
26
16
|
# Connected is called by the XmppConnection to indicate that the XMPP connection has been established
|
27
17
|
def connected(connection)
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
##
|
32
|
-
# Accessor for @@connection
|
33
|
-
def connection
|
34
|
-
@@connection
|
18
|
+
@connection = connection
|
35
19
|
end
|
36
20
|
|
37
21
|
##
|
38
|
-
# Insert a route and makes sure that the routes are sorted
|
39
|
-
def add_route(route)
|
40
|
-
@routes ||= []
|
41
|
-
@routes << route
|
42
|
-
sort
|
43
|
-
end
|
44
|
-
|
45
22
|
# Look for the first matching route and calls the corresponding action for the corresponding controller.
|
46
23
|
# Sends the response on the XMPP stream/
|
47
|
-
def route(
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
return true
|
65
|
-
end
|
66
|
-
}
|
67
|
-
false
|
68
|
-
end
|
69
|
-
|
70
|
-
# Throw away all added routes from this router. Helpful for
|
71
|
-
# testing.
|
72
|
-
def purge_routes!
|
73
|
-
@routes = []
|
24
|
+
def route(xml_stanza)
|
25
|
+
route = routes.select{ |r| r.accepts?(xml_stanza) }.first
|
26
|
+
return false unless route
|
27
|
+
execute_route(route.controller, route.action, xml_stanza)
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Executes the route for the given xml_stanza, by instantiating the controller_name, calling action_name and sending
|
32
|
+
# the result to the connection
|
33
|
+
def execute_route(controller_name, action_name, xml_stanza = nil)
|
34
|
+
Babylon.logger.info("ROUTING TO #{controller_name}::#{action_name}")
|
35
|
+
stanza = nil
|
36
|
+
stanza = Kernel.const_get(action_name.capitalize).new(xml_stanza) if xml_stanza
|
37
|
+
controller = controller_name.new(stanza)
|
38
|
+
controller.perform(action_name)
|
39
|
+
connection.send_xml(controller.evaluate)
|
74
40
|
end
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
41
|
+
|
42
|
+
##
|
43
|
+
# Throw away all added routes from this router. Helpful for
|
44
|
+
# testing.
|
45
|
+
def purge_routes!
|
46
|
+
@routes = []
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Run the router DSL.
|
51
|
+
def draw(&block)
|
52
|
+
dsl = Router::DSL.new
|
53
|
+
dsl.instance_eval(&block)
|
54
|
+
dsl.routes.each do |route|
|
55
|
+
raise("Route lacks destination: #{route.inspect}") unless route.is_a?(Route)
|
56
|
+
end
|
57
|
+
@routes += dsl.routes
|
85
58
|
sort
|
86
59
|
end
|
87
60
|
|
88
61
|
private
|
62
|
+
|
89
63
|
def sort
|
90
|
-
@routes.sort! { |r1,r2|
|
91
|
-
r2.priority <=> r1.priority
|
92
|
-
}
|
64
|
+
@routes.sort! { |r1,r2| r2.priority <=> r1.priority }
|
93
65
|
end
|
94
66
|
end
|
95
67
|
|
96
|
-
##
|
97
|
-
# Main router for a Babylon Application.
|
98
|
-
module CentralRouter
|
99
|
-
extend Router
|
100
|
-
end
|
101
|
-
|
102
68
|
##
|
103
69
|
# Route class which associate an XPATH match and a priority to a controller and an action
|
104
70
|
class Route
|
105
|
-
|
71
|
+
|
106
72
|
attr_accessor :priority, :controller, :action, :xpath
|
107
73
|
|
108
74
|
##
|
@@ -110,21 +76,17 @@ module Babylon
|
|
110
76
|
def initialize(params)
|
111
77
|
raise("No controller given for route") unless params["controller"]
|
112
78
|
raise("No action given for route") unless params["action"]
|
79
|
+
raise("No xpath given for route") unless params["xpath"]
|
113
80
|
@priority = params["priority"] || 0
|
114
|
-
@xpath = params["xpath"]
|
115
|
-
@css = params["css"] if params["css"]
|
81
|
+
@xpath = params["xpath"]
|
116
82
|
@controller = Kernel.const_get("#{params["controller"].capitalize}Controller")
|
117
83
|
@action = params["action"]
|
118
84
|
end
|
119
|
-
|
85
|
+
|
120
86
|
##
|
121
|
-
# Checks that the route matches the stanzas and calls the the action on the controller
|
87
|
+
# Checks that the route matches the stanzas and calls the the action on the controller.
|
122
88
|
def accepts?(stanza)
|
123
|
-
|
124
|
-
stanza.xpath(@xpath, XpathHelper.new).first ? self : false
|
125
|
-
elsif @css
|
126
|
-
stanza.css(@css).first ? self : false
|
127
|
-
end
|
89
|
+
stanza.xpath(@xpath, XpathHelper.new).empty? ? false : self
|
128
90
|
end
|
129
91
|
|
130
92
|
end
|
data/lib/babylon/router/dsl.rb
CHANGED
data/lib/babylon/runner.rb
CHANGED
@@ -4,6 +4,38 @@ module Babylon
|
|
4
4
|
# Runner is in charge of running the application.
|
5
5
|
class Runner
|
6
6
|
|
7
|
+
##
|
8
|
+
# Prepares the Application to run.
|
9
|
+
def self.prepare(env)
|
10
|
+
# Load the configuration
|
11
|
+
config_file = File.open('config/config.yaml')
|
12
|
+
Babylon.config = YAML.load(config_file)[Babylon.environment]
|
13
|
+
|
14
|
+
# Add an outputter to the logger
|
15
|
+
Babylon.logger.add(Log4r::FileOutputter.new("#{Babylon.environment}", :filename => "log/#{Babylon.environment}.log", :trunc => false))
|
16
|
+
|
17
|
+
# Requiring all models, stanza, controllers
|
18
|
+
['app/models/*.rb', 'app/stanzas/*.rb', 'app/controllers/*_controller.rb'].each do |dir|
|
19
|
+
Runner.require_directory(dir)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create the router
|
23
|
+
Babylon.router = Babylon::StanzaRouter.new
|
24
|
+
|
25
|
+
# Evaluate routes defined with the new DSL router.
|
26
|
+
require 'config/routes.rb'
|
27
|
+
|
28
|
+
# Caching views
|
29
|
+
Babylon.cache_views
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Convenience method to require files in a given directory
|
35
|
+
def self.require_directory(path)
|
36
|
+
Dir.glob(path).each { |f| require f }
|
37
|
+
end
|
38
|
+
|
7
39
|
##
|
8
40
|
# When run is called, it loads the configuration, the routes and add them into the router
|
9
41
|
# It then loads the models.
|
@@ -17,31 +49,7 @@ module Babylon
|
|
17
49
|
EventMachine.epoll
|
18
50
|
EventMachine.run do
|
19
51
|
|
20
|
-
|
21
|
-
Babylon.logger.add(Log4r::FileOutputter.new("#{Babylon.environment}", :filename => "log/#{Babylon.environment}.log", :trunc => false))
|
22
|
-
|
23
|
-
# Requiring all models
|
24
|
-
Dir.glob('app/models/*.rb').each { |f| require f }
|
25
|
-
|
26
|
-
# Requiring all stanzas
|
27
|
-
Dir.glob('app/stanzas/*.rb').each { |f| require f }
|
28
|
-
|
29
|
-
# Load the controllers
|
30
|
-
Dir.glob('app/controllers/*_controller.rb').each {|f| require f }
|
31
|
-
|
32
|
-
# Evaluate routes defined with the new DSL router.
|
33
|
-
CentralRouter.draw do
|
34
|
-
eval File.read("config/routes.rb")
|
35
|
-
end
|
36
|
-
|
37
|
-
config_file = File.open('config/config.yaml')
|
38
|
-
|
39
|
-
# Caching views in production mode.
|
40
|
-
if Babylon.environment == "production"
|
41
|
-
Babylon.cache_views
|
42
|
-
end
|
43
|
-
|
44
|
-
Babylon.config = YAML.load(config_file)[Babylon.environment]
|
52
|
+
Runner.prepare(env)
|
45
53
|
|
46
54
|
case Babylon.config["application_type"]
|
47
55
|
when "client"
|
@@ -63,18 +71,24 @@ module Babylon
|
|
63
71
|
|
64
72
|
##
|
65
73
|
# Adding a connection observer. These observer will receive on_connected and on_disconnected events.
|
66
|
-
def self.add_connection_observer(
|
67
|
-
@@observers ||= Array.new
|
68
|
-
|
74
|
+
def self.add_connection_observer(observer)
|
75
|
+
@@observers ||= Array.new
|
76
|
+
if observer.ancestors.include? Babylon::Base::Controller
|
77
|
+
Babylon.logger.debug("Added #{observer} to the list of Connection Observers")
|
78
|
+
@@observers.push(observer) unless @@observers.include? observer
|
79
|
+
else
|
80
|
+
Babylon.logger.error("Observer can only be Babylon::Base::Controller")
|
81
|
+
false
|
82
|
+
end
|
69
83
|
end
|
70
84
|
|
71
85
|
##
|
72
86
|
# Will be called by the connection class once it is connected to the server.
|
73
87
|
# It "plugs" the router and then calls on_connected on the various observers.
|
74
88
|
def self.on_connected(connection)
|
75
|
-
Babylon
|
76
|
-
connection_observers.each do |
|
77
|
-
|
89
|
+
Babylon.router.connected(connection)
|
90
|
+
connection_observers.each do |observer|
|
91
|
+
Babylon.router.execute_route(observer, "on_connected")
|
78
92
|
end
|
79
93
|
end
|
80
94
|
|
@@ -82,16 +96,24 @@ module Babylon
|
|
82
96
|
# Will be called by the connection class upon disconnection.
|
83
97
|
# It stops the event loop and then calls on_disconnected on the various observers.
|
84
98
|
def self.on_disconnected()
|
85
|
-
EventMachine.stop_event_loop
|
86
99
|
connection_observers.each do |conn_obs|
|
87
|
-
|
100
|
+
observer = conn_obs.new
|
101
|
+
observer.on_disconnected if observer.respond_to?("on_disconnected")
|
88
102
|
end
|
103
|
+
EventMachine.stop_event_loop
|
89
104
|
end
|
90
105
|
|
91
106
|
##
|
92
107
|
# Will be called by the connection class when it receives and parses a stanza.
|
93
108
|
def self.on_stanza(stanza)
|
94
|
-
|
109
|
+
begin
|
110
|
+
Babylon.router.route(stanza)
|
111
|
+
rescue Babylon::NotConnected
|
112
|
+
Babylon.logger.fatal("#{$!.class} => #{$!.inspect}\n#{$!.backtrace.join("\n")}")
|
113
|
+
EventMachine::stop_event_loop
|
114
|
+
rescue
|
115
|
+
Babylon.logger.error("#{$!.class} => #{$!.inspect}\n#{$!.backtrace.join("\n")}")
|
116
|
+
end
|
95
117
|
end
|
96
118
|
|
97
119
|
end
|
@@ -1,40 +1,61 @@
|
|
1
1
|
module Babylon
|
2
|
-
|
2
|
+
|
3
3
|
##
|
4
4
|
# Connection Exception
|
5
|
-
class NotConnected <
|
5
|
+
class NotConnected < StandardError; end
|
6
6
|
|
7
7
|
##
|
8
8
|
# xml-not-well-formed Exception
|
9
|
-
class XmlNotWellFormed <
|
9
|
+
class XmlNotWellFormed < StandardError; end
|
10
10
|
|
11
11
|
##
|
12
12
|
# Error when there is no connection to the host and port.
|
13
|
-
class NoConnection <
|
13
|
+
class NoConnection < StandardError; end
|
14
14
|
|
15
15
|
##
|
16
16
|
# Authentication Error (wrong password/jid combination). Used for Clients and Components
|
17
|
-
class AuthenticationError <
|
17
|
+
class AuthenticationError < StandardError; end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Raised when the application tries to send a stanza that might be rejected by the server because it's too long.
|
21
|
+
class StanzaTooBig < StandardError; end
|
18
22
|
|
19
23
|
##
|
20
24
|
# This class is in charge of handling the network connection to the XMPP server.
|
21
25
|
class XmppConnection < EventMachine::Connection
|
22
|
-
|
26
|
+
|
23
27
|
attr_accessor :jid, :host, :port
|
28
|
+
|
29
|
+
@@max_stanza_size = 65535
|
30
|
+
|
31
|
+
##
|
32
|
+
# Maximum Stanza size. Default is 65535
|
33
|
+
def self.max_stanza_size
|
34
|
+
@@max_stanza_size
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Setter for Maximum Stanza size.
|
39
|
+
def self.max_stanza_size=(_size)
|
40
|
+
@@max_stanza_size = _size
|
41
|
+
end
|
24
42
|
|
25
43
|
##
|
26
|
-
# Connects the XmppConnection to the right host with the right port.
|
44
|
+
# Connects the XmppConnection to the right host with the right port.
|
27
45
|
# It passes itself (as handler) and the configuration
|
28
46
|
# This can very well be overwritten by subclasses.
|
29
47
|
def self.connect(params, handler)
|
30
|
-
Babylon.logger.debug("CONNECTING TO #{params["host"]}:#{params["port"]}") # Very low level Logging
|
48
|
+
Babylon.logger.debug("CONNECTING TO #{params["host"]}:#{params["port"]} with #{handler.inspect} as connection handler") # Very low level Logging
|
31
49
|
begin
|
32
50
|
EventMachine.connect(params["host"], params["port"], self, params.merge({"handler" => handler}))
|
33
|
-
rescue
|
34
|
-
Babylon.logger.error("CONNECTION ERROR : #{$!}") # Very low level Logging
|
51
|
+
rescue RuntimeError
|
52
|
+
Babylon.logger.error("CONNECTION ERROR : #{$!.class} => #{$!}") # Very low level Logging
|
53
|
+
raise NotConnected
|
35
54
|
end
|
36
55
|
end
|
37
56
|
|
57
|
+
##
|
58
|
+
# Called when the connection is completed.
|
38
59
|
def connection_completed
|
39
60
|
@connected = true
|
40
61
|
Babylon.logger.debug("CONNECTED") # Very low level Logging
|
@@ -53,17 +74,22 @@ module Babylon
|
|
53
74
|
end
|
54
75
|
|
55
76
|
##
|
56
|
-
# Instantiate the Handler (called internally by EventMachine)
|
57
|
-
def initialize(params)
|
58
|
-
super()
|
77
|
+
# Instantiate the Handler (called internally by EventMachine)
|
78
|
+
def initialize(params = {})
|
59
79
|
@connected = false
|
60
|
-
@jid
|
61
|
-
@password
|
62
|
-
@host
|
63
|
-
@port
|
64
|
-
@handler
|
65
|
-
@
|
80
|
+
@jid = params["jid"]
|
81
|
+
@password = params["password"]
|
82
|
+
@host = params["host"]
|
83
|
+
@port = params["port"]
|
84
|
+
@handler = params["handler"]
|
85
|
+
@buffer = ""
|
66
86
|
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Attaches a new parser since the network connection has been established.
|
90
|
+
def post_init
|
91
|
+
@parser = XmppParser.new(method(:receive_stanza))
|
92
|
+
end
|
67
93
|
|
68
94
|
##
|
69
95
|
# Called when a full stanza has been received and returns it to the central router to be sent to the corresponding controller.
|
@@ -73,7 +99,6 @@ module Babylon
|
|
73
99
|
case stanza.name
|
74
100
|
when "stream:error"
|
75
101
|
if !stanza.children.empty? and stanza.children.first.name == "xml-not-well-formed"
|
76
|
-
# <stream:error><xml-not-well-formed xmlns:xmlns="urn:ietf:params:xml:ns:xmpp-streams"/></stream:error>
|
77
102
|
Babylon.logger.error("DISCONNECTED DUE TO MALFORMED STANZA")
|
78
103
|
raise XmlNotWellFormed
|
79
104
|
end
|
@@ -89,40 +114,25 @@ module Babylon
|
|
89
114
|
end
|
90
115
|
|
91
116
|
##
|
92
|
-
# Sends the Nokogiri::XML data (after converting to string) on the stream.
|
93
|
-
# This method also adds a "from" attribute to all stanza if it was ommited (the full jid) only if a "to" attribute is present. if not, we assume that we're speaking to the server and the server doesn't need a "from" to identify where the message is coming from.
|
117
|
+
# Sends the Nokogiri::XML data (after converting to string) on the stream. Eventually it displays this data for debugging purposes.
|
94
118
|
def send_xml(xml)
|
95
|
-
raise NotConnected unless @connected
|
96
119
|
if xml.is_a? Nokogiri::XML::NodeSet
|
97
|
-
xml.each do |
|
98
|
-
|
120
|
+
xml.each do |element|
|
121
|
+
send_chunk(element.to_s)
|
99
122
|
end
|
100
|
-
elsif xml.is_a? Nokogiri::XML::Node
|
101
|
-
send_node(xml)
|
102
123
|
else
|
103
|
-
|
104
|
-
send_string("#{xml}")
|
124
|
+
send_chunk(xml.to_s)
|
105
125
|
end
|
106
126
|
end
|
107
127
|
|
108
128
|
private
|
109
129
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
##
|
118
|
-
# Sends a string on the line
|
119
|
-
def send_string(string)
|
120
|
-
begin
|
121
|
-
Babylon.logger.debug("SENDING : #{string}")
|
122
|
-
send_data("#{string}")
|
123
|
-
rescue
|
124
|
-
Babylon.logger.error("#{$!}\n#{$!.backtrace.join("\n")}")
|
125
|
-
end
|
130
|
+
def send_chunk(string)
|
131
|
+
raise NotConnected unless @connected
|
132
|
+
return if string.blank?
|
133
|
+
raise StanzaTooBig if string.length > XmppConnection.max_stanza_size
|
134
|
+
Babylon.logger.debug("SENDING : " + string)
|
135
|
+
send_data string
|
126
136
|
end
|
127
137
|
|
128
138
|
##
|
@@ -130,7 +140,7 @@ module Babylon
|
|
130
140
|
def receive_data(data)
|
131
141
|
begin
|
132
142
|
Babylon.logger.debug("RECEIVED : #{data}")
|
133
|
-
@parser.push(data)
|
143
|
+
@parser.push(data)
|
134
144
|
rescue
|
135
145
|
Babylon.logger.error("#{$!}\n#{$!.backtrace.join("\n")}")
|
136
146
|
end
|