marilyn-rpc 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +39 -4
- data/examples/async/client.rb +0 -5
- data/examples/authentication/client.rb +18 -0
- data/examples/authentication/server.rb +30 -0
- data/lib/marilyn-rpc.rb +1 -1
- data/lib/marilyn-rpc/client.rb +21 -3
- data/lib/marilyn-rpc/error.rb +13 -0
- data/lib/marilyn-rpc/mails.rb +3 -3
- data/lib/marilyn-rpc/server.rb +6 -11
- data/lib/marilyn-rpc/service.rb +86 -11
- data/lib/marilyn-rpc/service_cache.rb +44 -23
- data/lib/marilyn-rpc/version.rb +1 -1
- data/spec/mails_spec.rb +2 -1
- data/spec/server_spec.rb +1 -1
- data/spec/service_cache_spec.rb +140 -0
- data/spec/service_spec.rb +56 -5
- data/spec/spec_helper.rb +5 -0
- metadata +10 -34
- data/doc/MarilynRPC.html +0 -108
- data/doc/MarilynRPC/CallRequestMail.html +0 -578
- data/doc/MarilynRPC/CallResponseMail.html +0 -418
- data/doc/MarilynRPC/Envelope.html +0 -705
- data/doc/MarilynRPC/ExceptionMail.html +0 -338
- data/doc/MarilynRPC/Gentleman.html +0 -658
- data/doc/MarilynRPC/MailFactory.html +0 -284
- data/doc/MarilynRPC/MailHelper.html +0 -489
- data/doc/MarilynRPC/NativeClient.html +0 -579
- data/doc/MarilynRPC/NativeClientProxy.html +0 -303
- data/doc/MarilynRPC/Server.html +0 -406
- data/doc/MarilynRPC/Service.html +0 -599
- data/doc/MarilynRPC/ServiceCache.html +0 -481
- data/doc/_index.html +0 -219
- data/doc/class_list.html +0 -36
- data/doc/css/common.css +0 -1
- data/doc/css/full_list.css +0 -53
- data/doc/css/style.css +0 -318
- data/doc/file.README.html +0 -154
- data/doc/file_list.html +0 -38
- data/doc/frames.html +0 -13
- data/doc/index.html +0 -154
- data/doc/js/app.js +0 -203
- data/doc/js/full_list.js +0 -149
- data/doc/js/jquery.js +0 -16
- data/doc/method_list.html +0 -467
- data/doc/top-level-namespace.html +0 -88
- data/spec/service_cache.rb +0 -45
data/README.md
CHANGED
@@ -9,10 +9,7 @@ The services are unique per connection, so if you have 50 connections, 50
|
|
9
9
|
service objects will be used, if (and only if) they are requested by the client.
|
10
10
|
|
11
11
|
Since this is a session dedicated to one connection, marilyn has support for per
|
12
|
-
connection caching by using instance variables.
|
13
|
-
enhance the capabilities of marilyn to allow connection based authentication.
|
14
|
-
Like in other protocols (e.g. IMAP) where some methods can be called
|
15
|
-
unauthenticated and some not.
|
12
|
+
connection caching by simply using instance variables.
|
16
13
|
|
17
14
|
Like in IMAP marilyn supports sending of multiple requests to a server over one
|
18
15
|
connection. This feature is called multiplexing supported by the current
|
@@ -122,6 +119,44 @@ The client also simply has to enable a secure connection:
|
|
122
119
|
|
123
120
|
client = MarilynRPC::NativeClient.connect_tcp('localhost', 8008, :secure => true)
|
124
121
|
|
122
|
+
## Authentication
|
123
|
+
|
124
|
+
If some remote service methods only should be called using a username/password
|
125
|
+
protection, one simply has to define which method and what mechanism:
|
126
|
+
|
127
|
+
MarilynRPC::Service.authenticate_with do |username, password|
|
128
|
+
username == "testuserid" && password == "secret"
|
129
|
+
end
|
130
|
+
|
131
|
+
class TestService < MarilynRPC::Service
|
132
|
+
register :test
|
133
|
+
authentication_required :add
|
134
|
+
|
135
|
+
def time # unauthenticated
|
136
|
+
puts session_username
|
137
|
+
puts session_authenticated?
|
138
|
+
Time.now
|
139
|
+
end
|
140
|
+
|
141
|
+
def add(a, b) # authenticated
|
142
|
+
puts session_username
|
143
|
+
puts session_authenticated?
|
144
|
+
a + b
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
The client simple has to do the authentication before start marking calls:
|
149
|
+
|
150
|
+
client = MarilynRPC::NativeClient.connect_tcp('localhost', 8000)
|
151
|
+
TestService = client.for(:test)
|
152
|
+
|
153
|
+
p TestService.time.to_f
|
154
|
+
client.authenticate "testuserid", "secret"
|
155
|
+
p TestService.add(1, 2)
|
156
|
+
|
157
|
+
client.disconnect
|
158
|
+
|
159
|
+
|
125
160
|
## Async Server Example & NativeClient
|
126
161
|
|
127
162
|
As previously said, the server can use the `Gentleman` to issue asynchronous
|
data/examples/async/client.rb
CHANGED
@@ -19,7 +19,6 @@ done = false
|
|
19
19
|
t3 = Thread.new do
|
20
20
|
while !done
|
21
21
|
sleep 1
|
22
|
-
p client
|
23
22
|
STDOUT.print "."
|
24
23
|
STDOUT.flush
|
25
24
|
end
|
@@ -29,10 +28,6 @@ t1.join
|
|
29
28
|
t2.join
|
30
29
|
|
31
30
|
done = true
|
32
|
-
t3.join
|
33
|
-
|
34
|
-
p client
|
35
|
-
|
36
31
|
end_time = Time.now
|
37
32
|
|
38
33
|
puts "#{start_time - end_time}"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
$:.push(File.join(File.dirname(__FILE__), "..", "..", "lib"))
|
2
|
+
require "marilyn-rpc"
|
3
|
+
client = MarilynRPC::NativeClient.connect_tcp('localhost', 8000)
|
4
|
+
TestService = client.for(:test)
|
5
|
+
|
6
|
+
begin
|
7
|
+
p TestService.add(1, 2)
|
8
|
+
rescue MarilynRPC::MarilynError => ex
|
9
|
+
puts "PermissionDenied: #{ex.message}"
|
10
|
+
end
|
11
|
+
|
12
|
+
p TestService.time.to_f
|
13
|
+
|
14
|
+
client.authenticate "testuserid", "secret"
|
15
|
+
|
16
|
+
p TestService.add(1, 2)
|
17
|
+
|
18
|
+
client.disconnect
|
@@ -0,0 +1,30 @@
|
|
1
|
+
$:.push(File.join(File.dirname(__FILE__), "..", "..", "lib"))
|
2
|
+
require "marilyn-rpc"
|
3
|
+
require "rubygems"
|
4
|
+
require "eventmachine"
|
5
|
+
|
6
|
+
MarilynRPC::Service.authenticate_with do |username, password|
|
7
|
+
username == "testuserid" && password == "secret"
|
8
|
+
end
|
9
|
+
|
10
|
+
class TestService < MarilynRPC::Service
|
11
|
+
register :test
|
12
|
+
authentication_required :add
|
13
|
+
|
14
|
+
def time
|
15
|
+
puts session_username
|
16
|
+
puts session_authenticated?
|
17
|
+
Time.now
|
18
|
+
end
|
19
|
+
|
20
|
+
def add(a, b)
|
21
|
+
puts session_username
|
22
|
+
puts session_authenticated?
|
23
|
+
a + b
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
EM.run {
|
29
|
+
EM.start_server "localhost", 8000, MarilynRPC::Server
|
30
|
+
}
|
data/lib/marilyn-rpc.rb
CHANGED
data/lib/marilyn-rpc/client.rb
CHANGED
@@ -2,11 +2,12 @@ require 'socket'
|
|
2
2
|
require 'thread'
|
3
3
|
|
4
4
|
module MarilynRPC
|
5
|
-
class
|
5
|
+
# A class with nothing but `__send__` and `__id__`
|
6
|
+
class ClientBlankSlate
|
6
7
|
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
7
8
|
end
|
8
9
|
|
9
|
-
class NativeClientProxy <
|
10
|
+
class NativeClientProxy < ClientBlankSlate
|
10
11
|
# Creates a new Native client proxy, were the calls get send to the remote
|
11
12
|
# side.
|
12
13
|
# @param [Object] path the path that is used to identify the service
|
@@ -62,6 +63,16 @@ module MarilynRPC
|
|
62
63
|
def disconnect
|
63
64
|
@socket.close
|
64
65
|
end
|
66
|
+
|
67
|
+
# authenicate the client to call methods that require authentication
|
68
|
+
# @param [String] username the username of the client
|
69
|
+
# @param [String] password the password of the client
|
70
|
+
# @param [Symbol] method the method to use for authentication, currently
|
71
|
+
# only plain is supported. So make sure you are using a secure socket.
|
72
|
+
def authenticate(username, password, method = :plain)
|
73
|
+
execute(MarilynRPC::Service::AUTHENTICATION_PATH,
|
74
|
+
"authenticate_#{method}".to_sym, [username, password])
|
75
|
+
end
|
65
76
|
|
66
77
|
# Creates a new Proxy Object for the connection.
|
67
78
|
# @param [Object] path the path were the service is registered on the remote
|
@@ -139,8 +150,15 @@ module MarilynRPC
|
|
139
150
|
if mail.is_a? MarilynRPC::CallResponseMail
|
140
151
|
mail.result
|
141
152
|
else
|
142
|
-
raise
|
153
|
+
raise MarilynError.new # raise exception to capture the client backtrace
|
143
154
|
end
|
155
|
+
rescue MarilynError => exception
|
156
|
+
# add local and remote trace together and reraise the original exception
|
157
|
+
backtrace = []
|
158
|
+
backtrace += exception.backtrace
|
159
|
+
backtrace += mail.exception.backtrace
|
160
|
+
mail.exception.set_backtrace(backtrace)
|
161
|
+
raise mail.exception
|
144
162
|
end
|
145
163
|
end
|
146
164
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module MarilynRPC
|
2
|
+
class MarilynError < StandardError; end
|
3
|
+
|
4
|
+
# Error that occurs, when we recieve an broken envelope
|
5
|
+
class BrokenEnvelopeError < MarilynError; end
|
6
|
+
|
7
|
+
# Error that occurs, when the client tries to call an unknown service
|
8
|
+
class UnknownServiceError < MarilynError; end
|
9
|
+
|
10
|
+
# Error that occurs, when the client tries to call an service, that requires
|
11
|
+
# authentication. The client must be authenticated before that call.
|
12
|
+
class PermissionDeniedError < MarilynError; end
|
13
|
+
end
|
data/lib/marilyn-rpc/mails.rb
CHANGED
@@ -61,11 +61,11 @@ module MarilynRPC
|
|
61
61
|
TYPE = MarilynRPC::MailHelper.type(3)
|
62
62
|
|
63
63
|
def encode
|
64
|
-
TYPE + serialize(self.exception)
|
64
|
+
TYPE + serialize([self.tag, self.exception])
|
65
65
|
end
|
66
66
|
|
67
67
|
def decode(data)
|
68
|
-
self.exception = deserialize(only_data(data))
|
68
|
+
self.tag, self.exception = deserialize(only_data(data))
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
@@ -86,7 +86,7 @@ module MarilynRPC
|
|
86
86
|
when MarilynRPC::ExceptionMail::TYPE
|
87
87
|
mail = MarilynRPC::ExceptionMail.new
|
88
88
|
else
|
89
|
-
raise
|
89
|
+
raise MarilynRPC::BrokenEnvelopeError.new("The passed envelope is broken!")
|
90
90
|
end
|
91
91
|
mail.decode(data)
|
92
92
|
mail
|
data/lib/marilyn-rpc/server.rb
CHANGED
@@ -34,17 +34,12 @@ module MarilynRPC::Server
|
|
34
34
|
|
35
35
|
# was massage parsed successfully?
|
36
36
|
if @envelope.finished?
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
answer =
|
41
|
-
|
42
|
-
|
43
|
-
else
|
44
|
-
answer.connection = self # pass connection for async responses
|
45
|
-
end
|
46
|
-
rescue => exception
|
47
|
-
send_mail(MarilynRPC::ExceptionMail.new(mail.tag, exception))
|
37
|
+
# grep the request
|
38
|
+
answer = @cache.call(@envelope)
|
39
|
+
if answer.is_a? MarilynRPC::Gentleman
|
40
|
+
answer.connection = self # pass connection for async responses
|
41
|
+
else
|
42
|
+
send_mail(answer)
|
48
43
|
end
|
49
44
|
|
50
45
|
# initialize the next envelope
|
data/lib/marilyn-rpc/service.rb
CHANGED
@@ -1,10 +1,19 @@
|
|
1
|
+
# A class with nothing but `__send__`, `__id__`, `class` and `public_methods`.
|
2
|
+
class ServiceBlankSlate
|
3
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__|public_methods|class/ }
|
4
|
+
end
|
5
|
+
|
1
6
|
# This class represents the base for all events, it is used for registering
|
2
|
-
# services and defining callbacks for certain service events.
|
7
|
+
# services and defining callbacks for certain service events. It is also
|
8
|
+
# possible to enable an authentication check for methods of the service.
|
9
|
+
# @attr [MarilynRPC::ServiceCache] service_cache the service cache where an
|
10
|
+
# instance lives in
|
3
11
|
# @example a service that makes use of the available helpers
|
4
12
|
# class EventsService < MarilynRPC::Service
|
5
13
|
# register :events
|
6
14
|
# after_connect :connected
|
7
15
|
# after_disconnect :disconnected
|
16
|
+
# authentication_required :notify
|
8
17
|
#
|
9
18
|
# def connected
|
10
19
|
# puts "client connected"
|
@@ -19,7 +28,9 @@
|
|
19
28
|
# end
|
20
29
|
# end
|
21
30
|
#
|
22
|
-
class MarilynRPC::Service
|
31
|
+
class MarilynRPC::Service < ServiceBlankSlate
|
32
|
+
attr_accessor :service_cache
|
33
|
+
|
23
34
|
# registers the class where is was called as a service
|
24
35
|
# @param [String] path the path of the service
|
25
36
|
def self.register(path)
|
@@ -31,7 +42,7 @@ class MarilynRPC::Service
|
|
31
42
|
# @api private
|
32
43
|
# @return [Hash<String, Object>] all registered services with path as key and
|
33
44
|
# the registered service as object
|
34
|
-
def self.
|
45
|
+
def self.__registry__
|
35
46
|
@@registry || {}
|
36
47
|
end
|
37
48
|
|
@@ -39,21 +50,21 @@ class MarilynRPC::Service
|
|
39
50
|
# defined in the class
|
40
51
|
# @param [Array<Symbol>, Array<String>] callbacks the method names
|
41
52
|
def self.after_connect(*callbacks)
|
42
|
-
|
53
|
+
__register_callbacks__ :after_connect, callbacks
|
43
54
|
end
|
44
55
|
|
45
56
|
# register one or more disconnect callbacks, a callback is simply a method
|
46
57
|
# defined in the class
|
47
58
|
# @param [Array<Symbol>, Array<String>] callbacks the method names
|
48
59
|
def self.after_disconnect(*callbacks)
|
49
|
-
|
60
|
+
__register_callbacks__ :after_disconnect, callbacks
|
50
61
|
end
|
51
62
|
|
52
63
|
# registers a callbacks for the service class
|
53
64
|
# @param [Symbol] name the name under which the callbacks should be saved
|
54
65
|
# @param [Array<Symbol>, Array<String>] callbacks the method names
|
55
66
|
# @api private
|
56
|
-
def self.
|
67
|
+
def self.__register_callbacks__(name, callbacks)
|
57
68
|
@_callbacks ||= {} # initialize callbacks
|
58
69
|
@_callbacks[name] ||= [] # initialize specific set
|
59
70
|
@_callbacks[name] += callbacks
|
@@ -64,16 +75,80 @@ class MarilynRPC::Service
|
|
64
75
|
# @return [Array<String>, Array<Symbol>] an array of callback names, or an
|
65
76
|
# empty array
|
66
77
|
# @api private
|
67
|
-
def self.
|
68
|
-
|
78
|
+
def self.__registered_callbacks__(name)
|
79
|
+
@_callbacks ||= {}
|
80
|
+
@_callbacks[name] || []
|
81
|
+
end
|
82
|
+
|
83
|
+
# this generator marks the passed method names to require authentication.
|
84
|
+
# A Method that requires authentication is only callable if the client was
|
85
|
+
# successfully authenticated.
|
86
|
+
# @param [Array<String>, Array<Symbol>] methods the methods names
|
87
|
+
def self.authentication_required(*methods)
|
88
|
+
@_authenticated ||= {} # initalize hash of authenticated methods
|
89
|
+
methods.each { |m| @_authenticated[m.to_sym] = true }
|
90
|
+
end
|
91
|
+
|
92
|
+
# returns all methods of the service that require authentication
|
93
|
+
# @return [Array<Symbol>] methods that require authentication
|
94
|
+
# @api private
|
95
|
+
def self.__methods_with_authentication__
|
96
|
+
@_authenticated ||= {}
|
69
97
|
end
|
70
98
|
|
71
99
|
# calls the defined connect callbacks
|
72
100
|
# @param [Symbol] the name of the callbacks to run
|
73
101
|
# @api private
|
74
|
-
def
|
75
|
-
self.class.
|
76
|
-
self.
|
102
|
+
def __run_callbacks__(name)
|
103
|
+
self.class.__registered_callbacks__(name).each do |callback|
|
104
|
+
self.__send__(callback)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# returns the username if a user is authenticated
|
109
|
+
# @return [String, nil] the username or nil, if no user is authenticated
|
110
|
+
def session_username
|
111
|
+
@service_cache.username
|
112
|
+
end
|
113
|
+
|
114
|
+
# checks if a user is authenticated
|
115
|
+
# @return [Boolean] `true` if a user is authenticated, `false` otherwise
|
116
|
+
def session_authenticated?
|
117
|
+
!@service_cache.username.nil?
|
118
|
+
end
|
119
|
+
|
120
|
+
# the name for the service which will be used to do the authentication
|
121
|
+
AUTHENTICATION_PATH = :__marilyn_rpc_service_authentication
|
122
|
+
|
123
|
+
# define an authentication mechanism using a `lambda` or `Proc` object, or
|
124
|
+
# something else that respond to `call`. The authentication is available for
|
125
|
+
# all serivces of that connection.
|
126
|
+
# @param [Proc] &authenticator the authentication mechanism
|
127
|
+
# @yieldparam [String] username the username of the client
|
128
|
+
# @yieldparam [String] password the password of the client
|
129
|
+
# @yieldreturn [Boolean] To authenticate a user, the passed
|
130
|
+
# block must return `true` otherwise `false`
|
131
|
+
# @example Create a new authentication mechanism for clients using callable
|
132
|
+
# MarilynRPC::Service.authenticate_with do |username, password|
|
133
|
+
# username == "testuserid" && password == "secret"
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
def self.authenticate_with(&authenticator)
|
137
|
+
Class.new(self) do # anonymous class
|
138
|
+
@@authenticator = authenticator
|
139
|
+
register(AUTHENTICATION_PATH)
|
140
|
+
|
141
|
+
# authenticate the user using a plain password method
|
142
|
+
# @param [String] username the username of the client
|
143
|
+
# @param [String] password the password of the client
|
144
|
+
def authenticate_plain(username, password)
|
145
|
+
if @@authenticator.call(username, password)
|
146
|
+
@service_cache.username = username
|
147
|
+
else
|
148
|
+
raise MarilynRPC::PermissionDeniedError.new \
|
149
|
+
"Wrong username or password!"
|
150
|
+
end
|
151
|
+
end
|
77
152
|
end
|
78
153
|
end
|
79
154
|
end
|
@@ -1,33 +1,53 @@
|
|
1
|
-
class
|
1
|
+
# # This class represents a per connection cache of the service instances.
|
2
|
+
# @attr [String, nil] username the username of a authenticated user oder `nil`
|
3
|
+
class MarilynRPC::ServiceCache
|
4
|
+
attr_accessor :username
|
5
|
+
|
2
6
|
# creates the service cache
|
3
7
|
def initialize
|
4
8
|
@services = {}
|
5
9
|
end
|
6
10
|
|
7
11
|
# call a service in the service cache
|
8
|
-
# @param [MarilynRPC::
|
9
|
-
# should be handled
|
12
|
+
# @param [MarilynRPC::Envelope] envelope the envelope that contains the
|
13
|
+
# request subject (mail), that should be handled
|
10
14
|
# @return [MarilynRPC::CallResponseMail, MarilynRPC::Gentleman] either a
|
11
15
|
# Gentleman if the response is async or an direct response.
|
12
|
-
def call(
|
13
|
-
|
14
|
-
|
15
|
-
raise ArgumentError.new("Expected CallRequestMail Object!")
|
16
|
-
end
|
17
|
-
|
18
|
-
# call the service instance using the argument of the mail
|
19
|
-
# puts "call #{mail.method}@#{mail.path} with #{mail.args.inspect}"
|
20
|
-
result = lookup(mail.path).send(mail.method, *mail.args)
|
21
|
-
# puts "result => #{result.inspect}"
|
16
|
+
def call(envelope)
|
17
|
+
mail = MarilynRPC::MailFactory.unpack(envelope)
|
18
|
+
tag = mail.tag
|
22
19
|
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
20
|
+
if mail.is_a?(MarilynRPC::CallRequestMail) # handle a call request
|
21
|
+
# fetch the service and check if the user has the permission to access the
|
22
|
+
# service
|
23
|
+
service = lookup(mail.path)
|
24
|
+
method = mail.method.to_sym
|
25
|
+
if service.class.__methods_with_authentication__[method] && !@username
|
26
|
+
raise MarilynRPC::PermissionDeniedError.new("No permission to access" \
|
27
|
+
" the #{service.class.name}##{method}")
|
28
|
+
end
|
29
|
+
|
30
|
+
# call the service instance using the argument of the mail
|
31
|
+
#puts "call #{mail.method}@#{mail.path} with #{mail.args.inspect}"
|
32
|
+
result = service.__send__(method, *mail.args)
|
33
|
+
#puts "result => #{result.inspect}"
|
34
|
+
|
35
|
+
# no direct result, register callback
|
36
|
+
if result.is_a? MarilynRPC::Gentleman
|
37
|
+
result.tag = tag # set the correct mail tag for the answer
|
38
|
+
result
|
39
|
+
else
|
40
|
+
MarilynRPC::CallResponseMail.new(tag, result) # direct response
|
41
|
+
end
|
27
42
|
else
|
28
|
-
|
29
|
-
MarilynRPC::CallResponseMail.new(mail.tag, result)
|
43
|
+
raise MarilynRPC::BrokenEnvelopeError.new("Expected CallRequestMail Object!")
|
30
44
|
end
|
45
|
+
rescue MarilynRPC::BrokenEnvelopeError => exception
|
46
|
+
MarilynRPC::ExceptionMail.new(nil, exception)
|
47
|
+
rescue => exception
|
48
|
+
#puts exception
|
49
|
+
#puts exception.backtrace.join("\n ")
|
50
|
+
MarilynRPC::ExceptionMail.new(tag, exception)
|
31
51
|
end
|
32
52
|
|
33
53
|
# get the service from the cache or the service registry
|
@@ -38,12 +58,13 @@ class MarilynRPC::ServiceCache
|
|
38
58
|
if service = @services[path]
|
39
59
|
return service
|
40
60
|
# it's not in the cache, so try lookup in the service registry
|
41
|
-
elsif service = MarilynRPC::Service.
|
61
|
+
elsif service = MarilynRPC::Service.__registry__[path]
|
42
62
|
@services[path] = service.new
|
43
|
-
@services[path].
|
63
|
+
@services[path].service_cache = self
|
64
|
+
@services[path].__run_callbacks__(:after_connect)
|
44
65
|
return @services[path]
|
45
66
|
else
|
46
|
-
raise
|
67
|
+
raise MarilynRPC::UnknownServiceError.new("Service #{path} unknown!")
|
47
68
|
end
|
48
69
|
end
|
49
70
|
|
@@ -52,7 +73,7 @@ class MarilynRPC::ServiceCache
|
|
52
73
|
# @api private
|
53
74
|
def disconnect!
|
54
75
|
@services.each do |path, service|
|
55
|
-
service.
|
76
|
+
service.__run_callbacks__(:after_disconnect)
|
56
77
|
end
|
57
78
|
end
|
58
79
|
end
|