marilyn-rpc 0.0.1 → 0.0.2
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/LICENCE +18 -0
- data/README.md +170 -0
- data/doc/MarilynRPC/CallRequestMail.html +578 -0
- data/doc/MarilynRPC/CallResponseMail.html +418 -0
- data/doc/MarilynRPC/Envelope.html +705 -0
- data/doc/MarilynRPC/ExceptionMail.html +338 -0
- data/doc/MarilynRPC/Gentleman.html +658 -0
- data/doc/MarilynRPC/MailFactory.html +284 -0
- data/doc/MarilynRPC/MailHelper.html +489 -0
- data/doc/MarilynRPC/NativeClient.html +579 -0
- data/doc/MarilynRPC/NativeClientProxy.html +303 -0
- data/doc/MarilynRPC/Server.html +406 -0
- data/doc/MarilynRPC/Service.html +599 -0
- data/doc/MarilynRPC/ServiceCache.html +481 -0
- data/doc/MarilynRPC.html +108 -0
- data/doc/_index.html +219 -0
- data/doc/class_list.html +36 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +53 -0
- data/doc/css/style.css +318 -0
- data/doc/file.README.html +154 -0
- data/doc/file_list.html +38 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +154 -0
- data/doc/js/app.js +203 -0
- data/doc/js/full_list.js +149 -0
- data/doc/js/jquery.js +16 -0
- data/doc/method_list.html +467 -0
- data/doc/top-level-namespace.html +88 -0
- data/examples/async/client.rb +40 -0
- data/examples/async/server.rb +26 -0
- data/examples/callbacks/client.rb +8 -0
- data/examples/callbacks/server.rb +26 -0
- data/examples/secure/client.rb +9 -0
- data/examples/secure/server.rb +22 -0
- data/kiss.png +0 -0
- data/lib/marilyn-rpc/client.rb +91 -26
- data/lib/marilyn-rpc/gentleman.rb +4 -1
- data/lib/marilyn-rpc/mails.rb +1 -1
- data/lib/marilyn-rpc/server.rb +27 -3
- data/lib/marilyn-rpc/service.rb +64 -0
- data/lib/marilyn-rpc/service_cache.rb +13 -2
- data/lib/marilyn-rpc/version.rb +1 -1
- metadata +39 -3
data/kiss.png
ADDED
Binary file
|
data/lib/marilyn-rpc/client.rb
CHANGED
@@ -1,35 +1,23 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'thread'
|
2
3
|
|
3
4
|
module MarilynRPC
|
4
|
-
class
|
5
|
+
class BlankSlate
|
6
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
7
|
+
end
|
8
|
+
|
9
|
+
class NativeClientProxy < BlankSlate
|
5
10
|
# Creates a new Native client proxy, were the calls get send to the remote
|
6
11
|
# side.
|
7
12
|
# @param [Object] path the path that is used to identify the service
|
8
|
-
# @param [
|
9
|
-
def initialize(path,
|
10
|
-
@path, @
|
13
|
+
# @param [NativeClient] client the client to use for communication
|
14
|
+
def initialize(path, client)
|
15
|
+
@path, @client = path, client
|
11
16
|
end
|
12
17
|
|
13
18
|
# Handler for calls to the remote system
|
14
19
|
def method_missing(method, *args, &block)
|
15
|
-
|
16
|
-
@socket.write(MarilynRPC::MailFactory.build_call(nil, @path, method, args))
|
17
|
-
|
18
|
-
# read the answer of the server back in
|
19
|
-
answer = MarilynRPC::Envelope.new
|
20
|
-
# read the header to have the size
|
21
|
-
answer.parse!(@socket.read(4))
|
22
|
-
# so now that we know the site, read the rest of the envelope
|
23
|
-
answer.parse!(@socket.read(answer.size))
|
24
|
-
|
25
|
-
# returns the result part of the mail or raise the exception if there is
|
26
|
-
# one
|
27
|
-
mail = MarilynRPC::MailFactory.unpack(answer)
|
28
|
-
if mail.is_a? MarilynRPC::CallResponseMail
|
29
|
-
mail.result
|
30
|
-
else
|
31
|
-
raise mail.exception
|
32
|
-
end
|
20
|
+
@client.execute(@path, method, args)
|
33
21
|
end
|
34
22
|
end
|
35
23
|
|
@@ -47,6 +35,27 @@ module MarilynRPC
|
|
47
35
|
# @param [Socket] socket the socket to manage
|
48
36
|
def initialize(socket)
|
49
37
|
@socket = socket
|
38
|
+
@semaphore = Mutex.new
|
39
|
+
@threads = {}
|
40
|
+
@responses = {}
|
41
|
+
@thread = Thread.new do
|
42
|
+
loop do
|
43
|
+
# read the answer of the server back in
|
44
|
+
answer = MarilynRPC::Envelope.new
|
45
|
+
# read the header to have the size
|
46
|
+
answer.parse!(@socket.read(4))
|
47
|
+
# so now that we know the site, read the rest of the envelope
|
48
|
+
answer.parse!(@socket.read(answer.size))
|
49
|
+
|
50
|
+
# returns the result part of the mail or raise the exception if there is
|
51
|
+
# one
|
52
|
+
mail = MarilynRPC::MailFactory.unpack(answer)
|
53
|
+
@semaphore.synchronize do
|
54
|
+
@responses[mail.tag] = mail # save the mail for the waiting thread
|
55
|
+
@threads.delete(mail.tag).wakeup # wake up the waiting thread
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
50
59
|
end
|
51
60
|
|
52
61
|
# Disconnect the client from the remote.
|
@@ -60,7 +69,7 @@ module MarilynRPC
|
|
60
69
|
# @return [MarilynRPC::NativeClientProxy] the proxy obejct that will serve
|
61
70
|
# all calls
|
62
71
|
def for(path)
|
63
|
-
NativeClientProxy.new(path,
|
72
|
+
NativeClientProxy.new(path, self)
|
64
73
|
end
|
65
74
|
|
66
75
|
# Connect to a unix domain socket.
|
@@ -73,9 +82,65 @@ module MarilynRPC
|
|
73
82
|
# Connect to a tcp socket.
|
74
83
|
# @param [String] host the host to cennect to (e.g. 'localhost')
|
75
84
|
# @param [Integer] port the port to connect to (e.g. 8000)
|
76
|
-
# @
|
77
|
-
|
78
|
-
|
85
|
+
# @param [Hash] options the
|
86
|
+
# @option options [Boolean] :secure use tls/ssl for the connection
|
87
|
+
# `true` or `false`
|
88
|
+
# @option options [OpenSSL::SSL::SSLContext] :ssl_context can be used to
|
89
|
+
# change the ssl context of the newly created secure connection. Only
|
90
|
+
# takes effect if `:secure` option is enabled.
|
91
|
+
# @return [MarilynRPC::NativeClient] the connected client
|
92
|
+
def self.connect_tcp(host, port, options = {})
|
93
|
+
if options[:secure] == true
|
94
|
+
require 'openssl' # use openssl for secure connections
|
95
|
+
socket = TCPSocket.new(host, port)
|
96
|
+
if ssl_context = options[:ssl_context]
|
97
|
+
secure_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
|
98
|
+
else
|
99
|
+
secure_socket = OpenSSL::SSL::SSLSocket.new(socket)
|
100
|
+
end
|
101
|
+
secure_socket.connect
|
102
|
+
new(secure_socket)
|
103
|
+
else
|
104
|
+
new(TCPSocket.open(host, port))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Executes a client call blocking. To issue an async call one needs to
|
109
|
+
# have start separate threads. THe Native client uses then multiplexing to
|
110
|
+
# avoid the other threads blocking.
|
111
|
+
# @api private
|
112
|
+
# @param [Object] path the path to identifiy the service
|
113
|
+
# @param [Symbol, String] method the method name to call on the service
|
114
|
+
# @param [Array<Object>] args the arguments that are passed to the remote
|
115
|
+
# side
|
116
|
+
# @return [Object] the result of the call
|
117
|
+
def execute(path, method, args)
|
118
|
+
thread = Thread.current
|
119
|
+
tag = "#{Time.now.to_f}:#{thread.object_id}"
|
120
|
+
|
121
|
+
@semaphore.synchronize do
|
122
|
+
# since this client can't multiplex, we set the tag to nil
|
123
|
+
@socket.write(MarilynRPC::MailFactory.build_call(tag, path, method, args))
|
124
|
+
end
|
125
|
+
|
126
|
+
# lets write our self to the list of waining threads
|
127
|
+
@semaphore.synchronize { @threads[tag] = thread }
|
128
|
+
|
129
|
+
# enable the listening for a response from the remote end
|
130
|
+
@thread.wakeup
|
131
|
+
|
132
|
+
# stop the current thread, the thread will be started after the response
|
133
|
+
# arrived
|
134
|
+
Thread.stop
|
135
|
+
|
136
|
+
# get mail from responses
|
137
|
+
mail = @semaphore.synchronize { @responses.delete(tag) }
|
138
|
+
|
139
|
+
if mail.is_a? MarilynRPC::CallResponseMail
|
140
|
+
mail.result
|
141
|
+
else
|
142
|
+
raise mail.exception
|
143
|
+
end
|
79
144
|
end
|
80
145
|
end
|
81
146
|
end
|
@@ -51,12 +51,15 @@ class MarilynRPC::Gentleman
|
|
51
51
|
# The handler that will send the response to the remote system
|
52
52
|
# @param [Object] args the arguments that should be handled by the callback,
|
53
53
|
# the reponse of the callback will be send as result
|
54
|
+
# @api private
|
54
55
|
def handle(*args)
|
55
56
|
mail = MarilynRPC::CallResponseMail.new(self.tag, self.callback.call(*args))
|
56
57
|
connection.send_mail(mail)
|
57
58
|
end
|
58
59
|
|
59
|
-
# The helper that will be called by the deferable to call
|
60
|
+
# The helper that will be called by the deferable to call
|
61
|
+
# {MarilynRPC::Gentleman#handle} later
|
62
|
+
# @api private
|
60
63
|
def helper
|
61
64
|
gentleman = self
|
62
65
|
lambda { |*args| gentleman.handle(*args) }
|
data/lib/marilyn-rpc/mails.rb
CHANGED
data/lib/marilyn-rpc/server.rb
CHANGED
@@ -1,9 +1,30 @@
|
|
1
|
+
# The server will be used to make incomming connections possible. The server
|
2
|
+
# handles the low level networking functions, so that the services don't have
|
3
|
+
# to deal with them. Because of the way eventmachine works you can have as many
|
4
|
+
# servers as you want.
|
5
|
+
#
|
6
|
+
# @example a server which is available througth 3 connections:
|
7
|
+
# EM.run {
|
8
|
+
# EM.start_server "localhost", 8000, MarilynRPC::Server
|
9
|
+
# EM.start_server "localhost", 8008, MarilynRPC::Server, :secure => true
|
10
|
+
# EM.start_unix_domain_server("tmp.socket", MarilynRPC::Server)
|
11
|
+
# }
|
12
|
+
#
|
1
13
|
module MarilynRPC::Server
|
14
|
+
# initalize the server with connection options
|
15
|
+
# @param [Hash] options the options passed to the server
|
16
|
+
# @option options [Boolean] :secure enable secure transfer for the server
|
17
|
+
# possible values `true` or `false`
|
18
|
+
def initialize(options = {})
|
19
|
+
@secure = options[:secure]
|
20
|
+
end
|
21
|
+
|
2
22
|
# Initialize the first recieving envelope for the connection and create the
|
3
23
|
# service cache since each connection gets it's own service instance.
|
4
24
|
def post_init
|
5
25
|
@envelope = MarilynRPC::Envelope.new
|
6
26
|
@cache = MarilynRPC::ServiceCache.new
|
27
|
+
start_tls if @secure
|
7
28
|
end
|
8
29
|
|
9
30
|
# Handler for the incoming data. EventMachine compatible.
|
@@ -15,14 +36,15 @@ module MarilynRPC::Server
|
|
15
36
|
if @envelope.finished?
|
16
37
|
begin
|
17
38
|
# grep the request
|
18
|
-
|
39
|
+
mail = MarilynRPC::MailFactory.unpack(@envelope)
|
40
|
+
answer = @cache.call(mail)
|
19
41
|
if answer.is_a? MarilynRPC::CallResponseMail
|
20
42
|
send_mail(answer)
|
21
43
|
else
|
22
44
|
answer.connection = self # pass connection for async responses
|
23
45
|
end
|
24
46
|
rescue => exception
|
25
|
-
send_mail(MarilynRPC::ExceptionMail.new(exception))
|
47
|
+
send_mail(MarilynRPC::ExceptionMail.new(mail.tag, exception))
|
26
48
|
end
|
27
49
|
|
28
50
|
# initialize the next envelope
|
@@ -39,5 +61,7 @@ module MarilynRPC::Server
|
|
39
61
|
end
|
40
62
|
|
41
63
|
# Handler for client disconnect
|
42
|
-
def unbind
|
64
|
+
def unbind
|
65
|
+
@cache.disconnect!
|
66
|
+
end
|
43
67
|
end
|
data/lib/marilyn-rpc/service.rb
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
# This class represents the base for all events, it is used for registering
|
2
|
+
# services and defining callbacks for certain service events.
|
3
|
+
# @example a service that makes use of the available helpers
|
4
|
+
# class EventsService < MarilynRPC::Service
|
5
|
+
# register :events
|
6
|
+
# after_connect :connected
|
7
|
+
# after_disconnect :disconnected
|
8
|
+
#
|
9
|
+
# def connected
|
10
|
+
# puts "client connected"
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# def notify(msg)
|
14
|
+
# puts msg
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def disconnected
|
18
|
+
# puts "client disconnected"
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
1
22
|
class MarilynRPC::Service
|
2
23
|
# registers the class where is was called as a service
|
3
24
|
# @param [String] path the path of the service
|
@@ -7,9 +28,52 @@ class MarilynRPC::Service
|
|
7
28
|
end
|
8
29
|
|
9
30
|
# returns all services, that where registered
|
31
|
+
# @api private
|
10
32
|
# @return [Hash<String, Object>] all registered services with path as key and
|
11
33
|
# the registered service as object
|
12
34
|
def self.registry
|
13
35
|
@@registry || {}
|
14
36
|
end
|
37
|
+
|
38
|
+
# register one or more connect callbacks, a callback is simply a method
|
39
|
+
# defined in the class
|
40
|
+
# @param [Array<Symbol>, Array<String>] callbacks the method names
|
41
|
+
def self.after_connect(*callbacks)
|
42
|
+
register_callbacks :after_connect, callbacks
|
43
|
+
end
|
44
|
+
|
45
|
+
# register one or more disconnect callbacks, a callback is simply a method
|
46
|
+
# defined in the class
|
47
|
+
# @param [Array<Symbol>, Array<String>] callbacks the method names
|
48
|
+
def self.after_disconnect(*callbacks)
|
49
|
+
register_callbacks :after_disconnect, callbacks
|
50
|
+
end
|
51
|
+
|
52
|
+
# registers a callbacks for the service class
|
53
|
+
# @param [Symbol] name the name under which the callbacks should be saved
|
54
|
+
# @param [Array<Symbol>, Array<String>] callbacks the method names
|
55
|
+
# @api private
|
56
|
+
def self.register_callbacks(name, callbacks)
|
57
|
+
@_callbacks ||= {} # initialize callbacks
|
58
|
+
@_callbacks[name] ||= [] # initialize specific set
|
59
|
+
@_callbacks[name] += callbacks
|
60
|
+
end
|
61
|
+
|
62
|
+
# returns the registered callbacks for name
|
63
|
+
# @param [Symbol] name the name to lookup callbacks for
|
64
|
+
# @return [Array<String>, Array<Symbol>] an array of callback names, or an
|
65
|
+
# empty array
|
66
|
+
# @api private
|
67
|
+
def self.registered_callbacks(name)
|
68
|
+
(@_callbacks || {})[name] || []
|
69
|
+
end
|
70
|
+
|
71
|
+
# calls the defined connect callbacks
|
72
|
+
# @param [Symbol] the name of the callbacks to run
|
73
|
+
# @api private
|
74
|
+
def run_callbacks!(name)
|
75
|
+
self.class.registered_callbacks(name).each do |callback|
|
76
|
+
self.send(callback)
|
77
|
+
end
|
78
|
+
end
|
15
79
|
end
|
@@ -39,9 +39,20 @@ class MarilynRPC::ServiceCache
|
|
39
39
|
return service
|
40
40
|
# it's not in the cache, so try lookup in the service registry
|
41
41
|
elsif service = MarilynRPC::Service.registry[path]
|
42
|
-
|
42
|
+
@services[path] = service.new
|
43
|
+
@services[path].run_callbacks! :after_connect
|
44
|
+
return @services[path]
|
43
45
|
else
|
44
|
-
raise ArgumentError.new("Service #{
|
46
|
+
raise ArgumentError.new("Service #{path} unknown!")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# issue the disconnect callbacks for all living services of this connection
|
51
|
+
# @node this should only be called once by the server
|
52
|
+
# @api private
|
53
|
+
def disconnect!
|
54
|
+
@services.each do |path, service|
|
55
|
+
service.run_callbacks! :after_disconnect
|
45
56
|
end
|
46
57
|
end
|
47
58
|
end
|
data/lib/marilyn-rpc/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: marilyn-rpc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Vincent Landgraf
|
@@ -62,6 +62,40 @@ extra_rdoc_files: []
|
|
62
62
|
files:
|
63
63
|
- benchmark/client.rb
|
64
64
|
- benchmark/server.rb
|
65
|
+
- doc/_index.html
|
66
|
+
- doc/class_list.html
|
67
|
+
- doc/css/common.css
|
68
|
+
- doc/css/full_list.css
|
69
|
+
- doc/css/style.css
|
70
|
+
- doc/file.README.html
|
71
|
+
- doc/file_list.html
|
72
|
+
- doc/frames.html
|
73
|
+
- doc/index.html
|
74
|
+
- doc/js/app.js
|
75
|
+
- doc/js/full_list.js
|
76
|
+
- doc/js/jquery.js
|
77
|
+
- doc/MarilynRPC/CallRequestMail.html
|
78
|
+
- doc/MarilynRPC/CallResponseMail.html
|
79
|
+
- doc/MarilynRPC/Envelope.html
|
80
|
+
- doc/MarilynRPC/ExceptionMail.html
|
81
|
+
- doc/MarilynRPC/Gentleman.html
|
82
|
+
- doc/MarilynRPC/MailFactory.html
|
83
|
+
- doc/MarilynRPC/MailHelper.html
|
84
|
+
- doc/MarilynRPC/NativeClient.html
|
85
|
+
- doc/MarilynRPC/NativeClientProxy.html
|
86
|
+
- doc/MarilynRPC/Server.html
|
87
|
+
- doc/MarilynRPC/Service.html
|
88
|
+
- doc/MarilynRPC/ServiceCache.html
|
89
|
+
- doc/MarilynRPC.html
|
90
|
+
- doc/method_list.html
|
91
|
+
- doc/top-level-namespace.html
|
92
|
+
- examples/async/client.rb
|
93
|
+
- examples/async/server.rb
|
94
|
+
- examples/callbacks/client.rb
|
95
|
+
- examples/callbacks/server.rb
|
96
|
+
- examples/secure/client.rb
|
97
|
+
- examples/secure/server.rb
|
98
|
+
- kiss.png
|
65
99
|
- lib/marilyn-rpc/client.rb
|
66
100
|
- lib/marilyn-rpc/envelope.rb
|
67
101
|
- lib/marilyn-rpc/gentleman.rb
|
@@ -71,7 +105,9 @@ files:
|
|
71
105
|
- lib/marilyn-rpc/service_cache.rb
|
72
106
|
- lib/marilyn-rpc/version.rb
|
73
107
|
- lib/marilyn-rpc.rb
|
108
|
+
- LICENCE
|
74
109
|
- marilyn-rpc.gemspec
|
110
|
+
- README.md
|
75
111
|
- spec/envelope_spec.rb
|
76
112
|
- spec/gentleman_spec.rb
|
77
113
|
- spec/mails_spec.rb
|