rubarb 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/README +77 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/examples/client.rb +32 -0
- data/examples/server.rb +34 -0
- data/lib/dkbrpc.rb +4 -0
- data/lib/rubarb.rb +2 -0
- data/lib/rubarb/connection.rb +170 -0
- data/lib/rubarb/connection_error.rb +11 -0
- data/lib/rubarb/connection_id.rb +11 -0
- data/lib/rubarb/default.rb +15 -0
- data/lib/rubarb/fast_message_protocol.rb +109 -0
- data/lib/rubarb/id.rb +13 -0
- data/lib/rubarb/incoming_connection.rb +25 -0
- data/lib/rubarb/insecure_method_call_error.rb +11 -0
- data/lib/rubarb/outgoing_connection.rb +27 -0
- data/lib/rubarb/remote_call.rb +12 -0
- data/lib/rubarb/responder.rb +18 -0
- data/lib/rubarb/server.rb +185 -0
- data/spec/rubarb/connection_failure_spec.rb +71 -0
- data/spec/rubarb/connection_spec.rb +187 -0
- data/spec/rubarb/id_spec.rb +26 -0
- data/spec/rubarb/incoming_connection_spec.rb +79 -0
- data/spec/rubarb/integration_spec.rb +174 -0
- data/spec/rubarb/outgoing_connection_spec.rb +103 -0
- data/spec/rubarb/remote_call_spec.rb +48 -0
- data/spec/rubarb/responder_spec.rb +35 -0
- data/spec/rubarb/server_failure_spec.rb +261 -0
- data/spec/rubarb/server_spec.rb +201 -0
- data/spec/spec_helper.rb +32 -0
- metadata +146 -0
data/lib/rubarb/id.rb
ADDED
@@ -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,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,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
|