rubarb 0.2.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/lib/rubarb/id.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Rubarb
2
+ class Id
3
+ def initialize
4
+ @id ||= 1
5
+ end
6
+
7
+ def next
8
+ id = "%08d" % @id
9
+ @id = @id == 99999999 ? 1 : @id + 1
10
+ id
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ require "rubarb/remote_call"
2
+ require "rubarb/responder"
3
+ require "rubarb/insecure_method_call_error"
4
+
5
+ module Rubarb
6
+
7
+ module IncomingConnection
8
+ include RemoteCall
9
+
10
+ def receive_message(message)
11
+ id, method, args = unmarshal_call(message)
12
+ responder = Responder.new(self, id)
13
+ begin
14
+ raise Rubarb::InsecureMethodCallError.new(method) if @insecure_methods.include?(method)
15
+ api.send(method, *[responder, *args]);
16
+ rescue Exception => e
17
+ reply("0", e)
18
+ end
19
+ end
20
+
21
+ def reply(id, *args)
22
+ send_message(marshal_call(args.unshift(id)))
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module Rubarb
2
+ class InsecureMethodCallError < StandardError
3
+ def initialize(method="")
4
+ @method = method
5
+ end
6
+
7
+ def message
8
+ "Remote client attempts to call method #{@method}, but was denied."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ require "rubarb/remote_call"
2
+
3
+ module Rubarb
4
+ module OutgoingConnection
5
+ include RemoteCall
6
+
7
+ def receive_message(message)
8
+ id, *unmarshaled_message = *unmarshal_call(message)
9
+ if unmarshaled_message.first.is_a?(Exception)
10
+ call_errbacks(*unmarshaled_message)
11
+ else
12
+ if @callback[id]
13
+ @callback[id].call(*unmarshaled_message)
14
+ @callback.delete(id)
15
+ end
16
+ end
17
+ end
18
+
19
+ def remote_call(method, *args, &block)
20
+ id = @msg_id_generator.next
21
+ @callback ||= {}
22
+ @callback[id] = block
23
+ send_message(marshal_call(id, method, *args))
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module Rubarb
2
+ module RemoteCall
3
+ def marshal_call(*args)
4
+ Marshal::dump(args)
5
+ end
6
+
7
+ def unmarshal_call(data)
8
+ return *Marshal::load(data)
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module Rubarb
2
+ class Responder
3
+ attr_reader :message_id
4
+ attr_reader :handler
5
+ def initialize(handler, message_id)
6
+ @handler = handler
7
+ @message_id = message_id
8
+ end
9
+
10
+ def conn_id
11
+ @handler.conn_id
12
+ end
13
+
14
+ def reply(*args)
15
+ @handler.reply(@message_id, *args)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,185 @@
1
+ require 'eventmachine'
2
+ require 'rubarb/connection_id'
3
+ require "rubarb/fast_message_protocol"
4
+ require 'rubarb/outgoing_connection'
5
+ require 'rubarb/incoming_connection'
6
+ require 'rubarb/id'
7
+ require 'rubarb/default'
8
+
9
+ module Rubarb
10
+ class ClientProxy
11
+ attr_reader :remote_connection
12
+
13
+ def initialize(outgoing_connection)
14
+ @remote_connection = outgoing_connection
15
+ end
16
+
17
+ def method_missing(method, * args, & block)
18
+ EventMachine::schedule do
19
+ begin
20
+ @remote_connection.remote_call(method, args, & block)
21
+ rescue Exception => e
22
+ @remote_connection.call_errbacks(e)
23
+ end
24
+ end
25
+ end
26
+
27
+ def errback(& block)
28
+ @remote_connection.errbacks << block if block
29
+ end
30
+
31
+ def conn_id
32
+ @remote_connection.conn_id
33
+ end
34
+
35
+ def stop
36
+ EventMachine::next_tick do
37
+ @remote_connection.close_connection
38
+ end
39
+ end
40
+ end
41
+
42
+ class Server
43
+ attr_reader :connections
44
+ attr_reader :conn_id_generator
45
+ attr_reader :msg_id_generator
46
+ attr_reader :errback
47
+ attr_reader :insecure_methods
48
+ attr_accessor :external_protocol
49
+
50
+ def initialize(host, port, api, insecure_methods=Default::INSECURE_METHODS)
51
+ @host = host
52
+ @port = port
53
+ @api = api
54
+ @connections = []
55
+ @unbind_block = Proc.new do |connection|
56
+ @connections.delete(connection)
57
+ end
58
+ @conn_id_generator = Id.new
59
+ @msg_id_generator = Id.new
60
+ @insecure_methods = insecure_methods
61
+ end
62
+
63
+ def start(& callback)
64
+ EventMachine::schedule do
65
+ begin
66
+ @server_signature = EventMachine::start_server(@host, @port, Listener) do |connection|
67
+ connection.conn_id_generator = @conn_id_generator
68
+ connection.msg_id_generator = @msg_id_generator
69
+ connection.api = @api
70
+ connection.new_connection_callback = callback
71
+ connection.errbacks = @errback.nil? ? [] : [@errback]
72
+ connection.unbindback = @unbind_block
73
+ connection.insecure_methods = @insecure_methods
74
+ connection.external_protocol = @external_protocol
75
+ @connections << connection
76
+ end
77
+ rescue Exception => e
78
+ @errback.call(e) if @errback
79
+ end
80
+ end
81
+ end
82
+
83
+
84
+ def stop(& callback)
85
+ EventMachine::schedule do
86
+ EventMachine::next_tick do
87
+ close_all_connections
88
+ stop_server(callback)
89
+ end
90
+ end
91
+ end
92
+
93
+ def errback(& block)
94
+ @errback = block
95
+ end
96
+
97
+ private #################################################################################
98
+ def close_all_connections
99
+ @connections.each do |connection|
100
+ connection.close_connection
101
+ end
102
+ end
103
+
104
+ def stop_server(callback)
105
+ if @server_signature
106
+ EventMachine::stop_server(@server_signature)
107
+ @server_signature = nil
108
+ callback.call(true) if callback
109
+ else
110
+ callback.call(false) if callback
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ module Listener
117
+ INCOMING_CONNECTION = "4"[0]
118
+ OUTGOING_CONNECTION = "5"[0]
119
+ attr_accessor :conn_id_generator
120
+ attr_reader :conn_id
121
+ attr_accessor :msg_id_generator
122
+ attr_accessor :api
123
+ attr_accessor :new_connection_callback
124
+ attr_accessor :callback
125
+ attr_accessor :errbacks
126
+ attr_accessor :unbindback
127
+ attr_accessor :insecure_methods
128
+ attr_accessor :external_protocol
129
+
130
+ include ConnectionId
131
+
132
+ def post_init
133
+ @buffer = ""
134
+ end
135
+
136
+ def receive_data data
137
+ @buffer << data
138
+ handshake(@buffer) if @conn_id.nil?
139
+ end
140
+
141
+ def unbind
142
+ call_errbacks(ConnectionError.new)
143
+ @unbindback.call(self) if @unbindback
144
+ end
145
+
146
+ def call_errbacks(message)
147
+ @errbacks.each do |e|
148
+ e.call(message)
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def handle_incoming
155
+ @conn_id = @conn_id_generator.next
156
+ self.extend(IncomingConnection)
157
+ switch_protocol
158
+ send_data(@conn_id)
159
+ end
160
+
161
+ def handle_outgoing(buffer)
162
+ if complete_id?(buffer[1..-1])
163
+ @conn_id = extract_id(buffer[1..-1])
164
+ self.extend(OutgoingConnection)
165
+ switch_protocol
166
+ send_message(@conn_id)
167
+ @new_connection_callback.call(ClientProxy.new(self)) if @new_connection_callback
168
+ end
169
+ end
170
+
171
+ def handshake(buffer)
172
+ if buffer[0] == INCOMING_CONNECTION
173
+ handle_incoming
174
+ elsif buffer[0] == OUTGOING_CONNECTION
175
+ handle_outgoing(buffer)
176
+ else
177
+ @external_protocol.handle_connection(buffer, self) if @external_protocol
178
+ end
179
+ end
180
+
181
+ def switch_protocol
182
+ Rubarb::FastMessageProtocol.install(self)
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ require 'rubarb/server'
4
+ require 'rubarb/connection'
5
+
6
+ describe "Connection Failures" do
7
+
8
+ before(:each) do
9
+ @reactor = start_reactor
10
+ end
11
+
12
+ after(:each) do
13
+ stop_reactor(@reactor)
14
+ end
15
+
16
+ it "should fail to connect" do
17
+ @connection = Rubarb::Connection.new("127.0.0.1", 9441, mock("client api"))
18
+
19
+ @errback_called = false
20
+ @connection.errback do |error|
21
+ @errback_called = true
22
+ error.class.should == Rubarb::ConnectionError
23
+ error.message.should == "Connection Failure"
24
+ end
25
+
26
+ @callback_called = false
27
+ @connection.start do |remote_end|
28
+ @callback_called = true
29
+ end
30
+
31
+ wait_for {@errback_called}
32
+
33
+ @errback_called.should == true
34
+ @callback_called.should == false
35
+ end
36
+
37
+ it "should fail after it has connected" do
38
+ @server = Rubarb::Server.new("127.0.0.1", 9441, mock("server"))
39
+ @connection = Rubarb::Connection.new("127.0.0.1", 9441, mock("client"))
40
+ @connection2 = Rubarb::Connection.new("127.0.0.1", 9441, mock("client"))
41
+
42
+ @server.start
43
+
44
+ @errback_called = false
45
+ @connection.errback do |error|
46
+ @errback_called = true
47
+ error.class.should == Rubarb::ConnectionError
48
+ error.message.should == "Connection Failure"
49
+ end
50
+
51
+ @errback2_called = false
52
+ @connection2.errback do |error|
53
+ @errback2_called = true
54
+ error.class.should == Rubarb::ConnectionError
55
+ error.message.should == "Connection Failure"
56
+ end
57
+
58
+ @connection.start do
59
+ @server.stop
60
+ end
61
+
62
+ @connection2.start
63
+
64
+ wait_for{@errback_called}
65
+ @errback_called.should be_true
66
+
67
+ wait_for{@errback2_called}
68
+ @errback2_called.should be_true
69
+ end
70
+
71
+ end
@@ -0,0 +1,187 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ require 'rubarb/server'
4
+ require "rubarb/connection"
5
+ require "rubarb/remote_call"
6
+ require "rubarb/default"
7
+
8
+ describe Rubarb::Connection do
9
+ CUSTOM_INSECURE_METHODS = [:==, :===, :=~]
10
+
11
+ before(:all) do
12
+ @reactor = start_reactor
13
+ end
14
+
15
+ after(:all) do
16
+ stop_reactor(@reactor)
17
+ end
18
+
19
+ it "has an instance of Rubarb::Id" do
20
+ @connection = Rubarb::Connection.new("host", "port", "api")
21
+ @connection.msg_id_generator.class.should == Rubarb::Id
22
+ end
23
+
24
+ it "should stop" do
25
+ @result = "blah"
26
+ @connection = Rubarb::Connection.new("host", "port", "api")
27
+ @connection.stop do |result|
28
+ @result = result
29
+ end
30
+
31
+ wait_for { @result == false }
32
+ @result.should == false
33
+ end
34
+
35
+ describe "with client and server connected" do
36
+
37
+ def connect(connection)
38
+ connected = false
39
+ connection.start do
40
+ connected = true
41
+ end
42
+ wait_for { connected }
43
+ return connection
44
+ end
45
+
46
+ before(:all) do
47
+ @server = Rubarb::Server.new("127.0.0.1", 9441, mock("server"))
48
+ @server.start
49
+ @connection = connect(Rubarb::Connection.new("127.0.0.1", 9441, mock("client")))
50
+ end
51
+
52
+ it "sets an instance of Rubarb::Id to remote_connection" do
53
+ @connection.remote_connection.msg_id_generator.class.should == Rubarb::Id
54
+ end
55
+
56
+ it "sets an instance of insecure_methods to remote_connection" do
57
+ @connection.remote_connection.insecure_methods.class.should == Array
58
+ end
59
+
60
+ it "has default insecure methods" do
61
+ @connection.remote_connection.insecure_methods.should == Rubarb::Default::INSECURE_METHODS
62
+ end
63
+
64
+ it "can accept custom insecure methods" do
65
+ connection = connect(Rubarb::Connection.new("127.0.0.1", 9441, mock("client"), CUSTOM_INSECURE_METHODS))
66
+ connection.remote_connection.insecure_methods.should == CUSTOM_INSECURE_METHODS
67
+ end
68
+
69
+ it "should stop after it's connected" do
70
+ connection = connect(Rubarb::Connection.new("127.0.0.1", 9441, mock("client")))
71
+
72
+ @result = "boo"
73
+ connection.stop do |result|
74
+ @result = result
75
+ end
76
+ wait_for{@result == true}
77
+ @result.should == true
78
+ end
79
+
80
+ it "doesn't exit the reactor loop when an exception occurs in Connection::method_missing" do
81
+ connection = connect(Rubarb::Connection.new("127.0.0.1", 9441, mock("client")))
82
+ error = nil
83
+ connection.errback {|e| error = e}
84
+ connection.remote_connection.should_receive(:remote_call).and_raise("Blah")
85
+
86
+ connection.foo
87
+ wait_for{error != nil}
88
+
89
+ error.to_s.should == "Blah"
90
+ EM.reactor_running?.should == true
91
+ end
92
+
93
+ it "should catch exceptions from connect" do
94
+ connection = Rubarb::Connection.new("127.0.0.1", 9441, mock("client"))
95
+ EventMachine.stub!(:connect).and_raise("Internal Java error")
96
+ errback_called = false
97
+ connection.errback do |e|
98
+ e.message.should == "Internal Java error"
99
+ errback_called = true
100
+ end
101
+ connection.start
102
+
103
+ wait_for{errback_called}
104
+
105
+ end
106
+
107
+ end
108
+
109
+ end
110
+
111
+ describe Rubarb::OutgoingHandler do
112
+ include Rubarb::OutgoingHandler
113
+ include Rubarb::RemoteCall
114
+
115
+ before(:each) do
116
+ @sent_data = ""
117
+ self.stub!(:send_data) do |data|
118
+ @sent_data << data
119
+ end
120
+ post_init
121
+ end
122
+
123
+ it "should send type on start" do
124
+ connection_completed
125
+ @sent_data.should == "4"
126
+ end
127
+
128
+ it "should open an outgoing connection with connection id" do
129
+ connection_completed
130
+ @host = "1.2.3.4"
131
+ @port = 2321
132
+ EventMachine.should_receive(:connect).with("1.2.3.4", 2321, Rubarb::IncomingHandler)
133
+ receive_data("0000")
134
+ receive_data("0001")
135
+ end
136
+
137
+ it "should send marshaled calls" do
138
+ @callback = {}
139
+ @sent_msg = ""
140
+ id_generator = mock("id")
141
+ id_generator.stub!(:next).and_return("00000001")
142
+ @msg_id_generator = id_generator
143
+ self.stub!(:send_message) { |msg| @sent_msg << msg }
144
+ remote_call(:foo, "bary")
145
+ @sent_msg.should == marshal_call("00000001", :foo, "bary")
146
+ end
147
+ end
148
+
149
+ describe Rubarb::IncomingHandler do
150
+ include Rubarb::IncomingHandler
151
+
152
+ before(:each) do
153
+ @sent_data = ""
154
+ self.stub!(:send_data) do |data|
155
+ @sent_data << data
156
+ end
157
+ post_init
158
+ end
159
+
160
+ it "should send type and id on connection made" do
161
+ @id = "00000001"
162
+ connection_completed
163
+ @sent_data.should == "500000001"
164
+ end
165
+
166
+ it "should call back when finished" do
167
+ callback = false
168
+ @on_connection = Proc.new { callback = true }
169
+ @id = "00000001"
170
+
171
+ connection_completed
172
+ callback.should == false
173
+
174
+ receive_message(@id)
175
+ callback.should == true
176
+ end
177
+
178
+ it "should errback if ids do not match" do
179
+ errback_msg = false
180
+ @errbacks = [Proc.new { |error| errback_msg = error.message }]
181
+ @id = "00000001"
182
+
183
+ connection_completed
184
+ receive_message("00000004")
185
+ errback_msg.should == "Handshake Failure"
186
+ end
187
+ end