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/.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
|