rubarb 0.2.0

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