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.
Files changed (44) hide show
  1. data/LICENCE +18 -0
  2. data/README.md +170 -0
  3. data/doc/MarilynRPC/CallRequestMail.html +578 -0
  4. data/doc/MarilynRPC/CallResponseMail.html +418 -0
  5. data/doc/MarilynRPC/Envelope.html +705 -0
  6. data/doc/MarilynRPC/ExceptionMail.html +338 -0
  7. data/doc/MarilynRPC/Gentleman.html +658 -0
  8. data/doc/MarilynRPC/MailFactory.html +284 -0
  9. data/doc/MarilynRPC/MailHelper.html +489 -0
  10. data/doc/MarilynRPC/NativeClient.html +579 -0
  11. data/doc/MarilynRPC/NativeClientProxy.html +303 -0
  12. data/doc/MarilynRPC/Server.html +406 -0
  13. data/doc/MarilynRPC/Service.html +599 -0
  14. data/doc/MarilynRPC/ServiceCache.html +481 -0
  15. data/doc/MarilynRPC.html +108 -0
  16. data/doc/_index.html +219 -0
  17. data/doc/class_list.html +36 -0
  18. data/doc/css/common.css +1 -0
  19. data/doc/css/full_list.css +53 -0
  20. data/doc/css/style.css +318 -0
  21. data/doc/file.README.html +154 -0
  22. data/doc/file_list.html +38 -0
  23. data/doc/frames.html +13 -0
  24. data/doc/index.html +154 -0
  25. data/doc/js/app.js +203 -0
  26. data/doc/js/full_list.js +149 -0
  27. data/doc/js/jquery.js +16 -0
  28. data/doc/method_list.html +467 -0
  29. data/doc/top-level-namespace.html +88 -0
  30. data/examples/async/client.rb +40 -0
  31. data/examples/async/server.rb +26 -0
  32. data/examples/callbacks/client.rb +8 -0
  33. data/examples/callbacks/server.rb +26 -0
  34. data/examples/secure/client.rb +9 -0
  35. data/examples/secure/server.rb +22 -0
  36. data/kiss.png +0 -0
  37. data/lib/marilyn-rpc/client.rb +91 -26
  38. data/lib/marilyn-rpc/gentleman.rb +4 -1
  39. data/lib/marilyn-rpc/mails.rb +1 -1
  40. data/lib/marilyn-rpc/server.rb +27 -3
  41. data/lib/marilyn-rpc/service.rb +64 -0
  42. data/lib/marilyn-rpc/service_cache.rb +13 -2
  43. data/lib/marilyn-rpc/version.rb +1 -1
  44. metadata +39 -3
data/kiss.png ADDED
Binary file
@@ -1,35 +1,23 @@
1
1
  require 'socket'
2
+ require 'thread'
2
3
 
3
4
  module MarilynRPC
4
- class NativeClientProxy
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 [Socekt] socket the socket to use for communication
9
- def initialize(path, socket)
10
- @path, @socket = path, socket
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
- # since this client can't multiplex, we set the tag to nil
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, @socket)
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
- # @return [MarilynRPC::NativeClient] the cónnected client
77
- def self.connect_tcp(host, port)
78
- new(TCPSocket.open(host, port))
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 {handle} later
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) }
@@ -56,7 +56,7 @@ module MarilynRPC
56
56
  end
57
57
  end
58
58
 
59
- class ExceptionMail < Struct.new(:exception)
59
+ class ExceptionMail < Struct.new(:tag, :exception)
60
60
  include MarilynRPC::MailHelper
61
61
  TYPE = MarilynRPC::MailHelper.type(3)
62
62
 
@@ -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
- answer = @cache.call(MarilynRPC::MailFactory.unpack(@envelope))
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; end
64
+ def unbind
65
+ @cache.disconnect!
66
+ end
43
67
  end
@@ -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
- return (@services[path] = service.new)
42
+ @services[path] = service.new
43
+ @services[path].run_callbacks! :after_connect
44
+ return @services[path]
43
45
  else
44
- raise ArgumentError.new("Service #{mail.path} unknown!")
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
@@ -1,3 +1,3 @@
1
1
  module MarilynRPC
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
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: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
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