rubarb 0.2.0

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