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 ADDED
@@ -0,0 +1,7 @@
1
+ dkbrpc.i*
2
+ rubarb.i*
3
+ pkg
4
+ .idea
5
+ *.iml
6
+ .rakeTasks
7
+ *.gemspec
data/README ADDED
@@ -0,0 +1,77 @@
1
+ rubaRb
2
+ A Bidirectional Event Machine Based Remote Procedure Call Library for Ruby
3
+
4
+ This library uses two socket connections between a client and a server.
5
+ One is used for request / replies from the client to the server.
6
+ The other is used for remote calls made from the server to the client.
7
+
8
+ Each end publishes a single object on which methods can be called by the remote end.
9
+ All calls to the remote objects are asyncronous. Do not make any blocking calls in
10
+ the published object. Responses are return by calling the "reply method on the responder object.
11
+
12
+ Server and Connection object may be created and started outside of EM::run,
13
+ but the Eventmachine reactor must be started somewhere in your application
14
+
15
+ ################################# Server Example #####################################################################
16
+
17
+ class ServerApi
18
+ def time(responder)
19
+ puts "Server received time request"
20
+ responder.reply(Time.now)
21
+ end
22
+ end
23
+
24
+ EM.run do
25
+ server = Rubarb::Server.new("127.0.0.1", 9441, ServerApi.new)
26
+
27
+ connections = {}
28
+
29
+ server.start do |client|
30
+ puts "Connection Made: #{client}"
31
+ client.name do |name|
32
+ connections[name] = client
33
+ client.errback do
34
+ puts "Connection Lost: #{name}"
35
+ connections.delete(name)
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ EventMachine.add_periodic_timer(1) { puts "Connections: #{connections.keys.inspect}" }
43
+
44
+ end
45
+ ######################################################################################################################
46
+
47
+
48
+ ################################# Client Example #####################################################################
49
+ class ClientApi
50
+ def initialize(name)
51
+ @name = name
52
+ end
53
+ def name(responder)
54
+ responder.reply(@name)
55
+ end
56
+ end
57
+
58
+ EM::run do
59
+ connection = Rubarb::Connection.new("127.0.0.1", 9441, ClientApi.new(ARGV[0]))
60
+ connection.errback do |error|
61
+ puts ("Connection Error: #{error}")
62
+ end
63
+
64
+ connection.start do
65
+ connection.time do |response|
66
+ puts "Server Said it is: #{response.strftime("%D")}"
67
+ end
68
+
69
+ EventMachine.add_timer(20) do
70
+ puts "stopping"
71
+ connection.stop
72
+ EM::stop
73
+ end
74
+ end
75
+
76
+ end
77
+ ######################################################################################################################
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rubarb"
8
+ gem.summary = %Q{A Bidirectional Event Machine Based Remote Procedure Call Library for Ruby}
9
+ gem.description = %Q{This library uses two socket connections between a client and a server. One is used for request / replies from the
10
+ client to the server. The other is used for remote calls made from the server to the client.
11
+
12
+ Each end publishes a single object on which
13
+ methods can be called by the remote end. All calls to the remote objects are asyncronous. Do not make any blocking
14
+ calls in the published object. Responses are return by calling the "reply method on the responder object.}
15
+
16
+ gem.email = "doug@8thlight.com"
17
+ gem.homepage = "http://github.com/dougbradbury/rubarb"
18
+ gem.authors = ["doug bradbury"]
19
+ gem.add_development_dependency "rspec", ">= 1.3.0"
20
+ gem.add_dependency "eventmachine", ">= 0.12.11"
21
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
22
+ end
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
25
+ end
26
+
27
+ require 'spec/rake/spectask'
28
+ Spec::Rake::SpecTask.new(:spec) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.spec_files = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
34
+ spec.libs << 'lib' << 'spec'
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :spec => :check_dependencies
40
+
41
+ task :default => :spec
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "rubarb-gem #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,32 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+ require 'rubygems'
3
+ require 'rubarb'
4
+
5
+ class ClientApi
6
+ def initialize(name)
7
+ @name = name
8
+ end
9
+ def name(responder)
10
+ responder.reply(@name)
11
+ end
12
+ end
13
+
14
+ EM::run do
15
+ connection = Rubarb::Connection.new("127.0.0.1", 9441, ClientApi.new(ARGV[0]))
16
+ connection.errback do |error|
17
+ puts ("Connection Error: #{error}")
18
+ end
19
+
20
+ connection.start do
21
+ connection.time do |response|
22
+ puts "Server Said it is: #{response.strftime("%D")}"
23
+ end
24
+
25
+ EventMachine.add_timer(20) do
26
+ puts "stopping"
27
+ connection.stop
28
+ EM::stop
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,34 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+ #$DEBUG = true
3
+ require 'rubygems'
4
+ require 'rubarb'
5
+
6
+
7
+ class ServerApi
8
+ def time(responder)
9
+ puts "Server received time request"
10
+ responder.reply(Time.now)
11
+ end
12
+ end
13
+
14
+ EM.run do
15
+ server = Rubarb::Server.new("127.0.0.1", 9441, ServerApi.new)
16
+
17
+ connections = {}
18
+
19
+ server.start do |client|
20
+ puts "Connection Made: #{client}"
21
+ client.name do |name|
22
+ connections[name] = client
23
+ client.errback do
24
+ puts "Connection Lost: #{name}"
25
+ connections.delete(name)
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ EventMachine.add_periodic_timer(1) { puts "Connections: #{connections.keys.inspect}" }
33
+
34
+ end
data/lib/dkbrpc.rb ADDED
@@ -0,0 +1,4 @@
1
+ #provided for backwards compatiblity with old name of gem
2
+ require "rubarb/connection"
3
+ require "rubarb/server"
4
+ Dkbrpc = Rubarb
data/lib/rubarb.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "rubarb/connection"
2
+ require "rubarb/server"
@@ -0,0 +1,170 @@
1
+ require "rubarb/connection_id"
2
+ require "rubarb/fast_message_protocol"
3
+ require "rubarb/remote_call"
4
+ require 'rubarb/outgoing_connection'
5
+ require 'rubarb/incoming_connection'
6
+ require 'rubarb/connection_error'
7
+ require 'rubarb/default'
8
+
9
+ module Rubarb
10
+ module IncomingHandler
11
+ include ConnectionId
12
+
13
+ attr_accessor :id
14
+ attr_accessor :on_connection
15
+ attr_accessor :api
16
+ attr_accessor :errbacks
17
+ attr_accessor :insecure_methods
18
+
19
+ def post_init
20
+ @buffer = ""
21
+ end
22
+
23
+ def connection_completed
24
+ Rubarb::FastMessageProtocol.install(self)
25
+ send_data("5")
26
+ send_data(@id)
27
+ end
28
+
29
+ def receive_message message
30
+ if (message == @id)
31
+ self.extend(Rubarb::IncomingConnection)
32
+ @on_connection.call if @on_connection
33
+ else
34
+ call_errbacks(ConnectionError.new("Handshake Failure"))
35
+ end
36
+ end
37
+
38
+ def unbind
39
+ call_errbacks(ConnectionError.new)
40
+ end
41
+
42
+ def call_errbacks(message)
43
+ @errbacks.each do |e|
44
+ e.call(message)
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ module OutgoingHandler
51
+ include ConnectionId
52
+ include OutgoingConnection
53
+ attr_accessor :host
54
+ attr_accessor :port
55
+ attr_accessor :on_connection
56
+ attr_accessor :api
57
+ attr_accessor :errbacks
58
+ attr_accessor :callback
59
+ attr_accessor :msg_id_generator
60
+ attr_accessor :insecure_methods
61
+
62
+ def post_init
63
+ @buffer = ""
64
+ end
65
+
66
+ def connection_completed
67
+ send_data("4")
68
+ end
69
+
70
+ def receive_data data
71
+ @buffer << data
72
+ if @id.nil?
73
+ handshake(@buffer)
74
+ end
75
+ end
76
+
77
+ def unbind
78
+ if @incoming_connection
79
+ EM.next_tick { @incoming_connection.close_connection }
80
+ else
81
+ call_errbacks(ConnectionError.new)
82
+ end
83
+ end
84
+
85
+ def call_errbacks(message)
86
+ @errbacks.each do |e|
87
+ e.call(message)
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def handshake(buffer)
94
+ if complete_id?(buffer)
95
+ Rubarb::FastMessageProtocol.install(self)
96
+ EventMachine::connect(@host, @port, IncomingHandler) do |incoming_connection|
97
+ @id = extract_id(buffer)
98
+ incoming_connection.id = @id
99
+ incoming_connection.on_connection = @on_connection
100
+ incoming_connection.api = @api
101
+ incoming_connection.errbacks = @errbacks
102
+ incoming_connection.insecure_methods = @insecure_methods
103
+ @incoming_connection = incoming_connection
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ class Connection
110
+ attr_reader :remote_connection
111
+ attr_reader :msg_id_generator
112
+
113
+ def initialize(host, port, api, insecure_methods=Default::INSECURE_METHODS)
114
+ @host = host
115
+ @port = port
116
+ @api = api
117
+ @msg_id_generator = Id.new
118
+ @errbacks = []
119
+ @insecure_methods = insecure_methods
120
+ end
121
+
122
+ def errback & block
123
+ @errbacks << block if block
124
+ end
125
+
126
+ def start & block
127
+ EventMachine::schedule do
128
+ begin
129
+ EventMachine::connect(@host, @port, OutgoingHandler) do |connection|
130
+ connection.host = @host
131
+ connection.port = @port
132
+ connection.on_connection = block
133
+ connection.api = @api
134
+ connection.errbacks = @errbacks
135
+ connection.msg_id_generator = @msg_id_generator
136
+ connection.insecure_methods = @insecure_methods
137
+ @remote_connection = connection
138
+ end
139
+ rescue Exception => e
140
+ @errbacks.each do |errback|
141
+ errback.call(e)
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ def method_missing(method, * args, & block)
148
+ EventMachine::schedule do
149
+ begin
150
+ @remote_connection.remote_call(method, args, & block)
151
+ rescue Exception => e
152
+ @remote_connection.call_errbacks(e)
153
+ end
154
+ end
155
+ end
156
+
157
+ def stop(& callback)
158
+ EventMachine::schedule do
159
+ if @remote_connection
160
+ EventMachine::next_tick do
161
+ @remote_connection.close_connection
162
+ callback.call(true) if callback
163
+ end
164
+ else
165
+ callback.call(false) if callback
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,11 @@
1
+ module Rubarb
2
+ class ConnectionError < StandardError
3
+ def initialize(message = "Connection Failure")
4
+ @message = message
5
+ end
6
+
7
+ def message
8
+ @message
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module ConnectionId
2
+ def complete_id?(data)
3
+ return false if data.size < 8
4
+ return true
5
+ end
6
+
7
+ def extract_id(data)
8
+ return data[0..7]
9
+ end
10
+
11
+ end
@@ -0,0 +1,15 @@
1
+ module Rubarb
2
+ module Default
3
+ INSECURE_METHODS = [:==, :===, :=~, :__drbref, :__drburi, :__id__, :__send__, :_dump,
4
+ :class, :clone, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze,
5
+ :frozen?, :hash, :id, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_variable_defined?,
6
+ :instance_variable_get, :instance_variable_set, :is_a?, :kind_of?, :method, :method_missing,
7
+ :methods, :nil?, :object_id, :pretty_print, :pretty_print_cycle, :private_methods,
8
+ :protected_methods, :public_methods, :respond_to?, :send, :singleton_methods,
9
+ :taint, :tainted?, :tap, :to_enum, :type, :untaint,
10
+ :add_options, :args_and_options, :as_null_object, :com, :config, :config=, :context, :dclone, :debugger,
11
+ :handle_different_imports, :hash, :include_class, :java, :java_kind_of?, :javax, :log, :null_object?, :org,
12
+ :received_message?, :taguri, :taguri=, :taint, :tainted?, :tap, :timeout, :to_a, :to_channel, :to_inputstream,
13
+ :to_outputstream, :to_s, :to_yaml, :to_yaml_properties, :to_yaml_style]
14
+ end
15
+ end
@@ -0,0 +1,109 @@
1
+ module Rubarb
2
+ # Receives data with a 4-byte integer size prefix (network byte order).
3
+ # Underlying protocol must implement #send_data and invoke #receive_data.
4
+ # User's protocol must call #send_message and listen to #receive_message callback.
5
+ module FastMessageProtocol
6
+
7
+ def self.install(object)
8
+ object.extend Rubarb::FastMessageProtocol
9
+ object.post_init
10
+ end
11
+
12
+ def post_init
13
+ @fmp_size = 0 # if 0, we're waiting for a new message,
14
+ # else - accumulating data.
15
+ @fmp_size_chunk = "" # we store a part of size chunk here
16
+ @fmp_data = ""
17
+ end
18
+
19
+ LENGTH_FORMAT = "N".freeze
20
+ LENGTH_FORMAT_LENGTH = 4
21
+
22
+ def send_message(data)
23
+ size = data.size
24
+ packed_size = [size].pack(LENGTH_FORMAT)
25
+ send_data packed_size
26
+ send_data data
27
+ end
28
+
29
+ def receive_data(next_chunk)
30
+ log {"fmp rx: #{next_chunk}"}
31
+ while true # helps fight deep recursion when receiving many messages in a single buffer.
32
+ data = next_chunk
33
+ # accumulate data
34
+ if @fmp_size > 0
35
+ left = @fmp_size - @fmp_data.size
36
+ now = data.size
37
+ log { "Need more #{left} bytes, got #{now} for now." }
38
+
39
+ if left > now
40
+ @fmp_data << data
41
+ break
42
+ elsif left == now
43
+ @fmp_data << data
44
+ data = @fmp_data
45
+ @fmp_data = ""
46
+ @fmp_size = 0
47
+ @fmp_size_chunk = ""
48
+ receive_message(data)
49
+ break
50
+ else
51
+ # Received more, than expected.
52
+ # 1. Forward expected part
53
+ # 2. Put unexpected part into receive_data
54
+ @fmp_data << data[0, left]
55
+ next_chunk = data[left, now]
56
+ data = @fmp_data
57
+ @fmp_data = ""
58
+ @fmp_size = 0
59
+ @fmp_size_chunk = ""
60
+ log { "Returning #{data.size} bytes (#{data[0..32]})" }
61
+ receive_message(data)
62
+ # (see while true: processing next chunk without recursive calls)
63
+ end
64
+
65
+ # get message size prefix
66
+ else
67
+ left = LENGTH_FORMAT_LENGTH - @fmp_size_chunk.size
68
+ now = data.size
69
+ log { "Need more #{left} bytes for size_chunk, got #{now} for now." }
70
+
71
+ if left > now
72
+ @fmp_size_chunk << data
73
+ break
74
+ elsif left == now
75
+ @fmp_size_chunk << data
76
+ @fmp_size = @fmp_size_chunk.unpack(LENGTH_FORMAT)[0]
77
+ log { "Ready to receive #{@fmp_size} bytes." }
78
+ break
79
+ else
80
+ # Received more, than expected.
81
+ # 1. Pick only expected part for length
82
+ # 2. Pass unexpected part into receive_data
83
+ @fmp_size_chunk << data[0, left]
84
+ next_chunk = data[left, now]
85
+ @fmp_size = @fmp_size_chunk.unpack(LENGTH_FORMAT)[0]
86
+ log { "Ready to receive #{@fmp_size} bytes." }
87
+ # (see while true) receive_data(next_chunk) # process next chunk
88
+ end # if
89
+ end # if
90
+ end # while true
91
+ end
92
+
93
+ # def receive_data
94
+
95
+ def err
96
+ STDERR.write("FastMessageProtocol: #{yield}\n")
97
+ end
98
+
99
+ def log
100
+ puts("FastMessageProtocol: #{yield}")
101
+ end
102
+
103
+ # Switch logging off when not in debug mode.
104
+ unless ($DEBUG || ENV['DEBUG']) && !($NO_FMP_DEBUG || ENV['NO_FMP_DEBUG'])
105
+ def log
106
+ end
107
+ end
108
+ end # module FastMessageProtocol
109
+ end # EMRPC