julien51-babylon 0.0.14 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|