marilyn-rpc 0.0.3 → 0.0.4

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/benchmark/client.rb +42 -14
  2. data/doc/MarilynRPC.html +108 -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/_index.html +219 -0
  16. data/doc/class_list.html +36 -0
  17. data/doc/css/common.css +1 -0
  18. data/doc/css/full_list.css +53 -0
  19. data/doc/css/style.css +318 -0
  20. data/doc/file.README.html +154 -0
  21. data/doc/file_list.html +38 -0
  22. data/doc/frames.html +13 -0
  23. data/doc/index.html +154 -0
  24. data/doc/js/app.js +203 -0
  25. data/doc/js/full_list.js +149 -0
  26. data/doc/js/jquery.js +16 -0
  27. data/doc/method_list.html +467 -0
  28. data/doc/top-level-namespace.html +88 -0
  29. data/lib/marilyn-rpc/client.rb +20 -21
  30. data/lib/marilyn-rpc/envelope.rb +27 -10
  31. data/lib/marilyn-rpc/gentleman.rb +8 -1
  32. data/lib/marilyn-rpc/mails.rb +40 -55
  33. data/lib/marilyn-rpc/server.rb +4 -11
  34. data/lib/marilyn-rpc/service.rb +1 -1
  35. data/lib/marilyn-rpc/service_cache.rb +7 -4
  36. data/lib/marilyn-rpc/version.rb +1 -1
  37. data/marilyn-rpc-0.0.2.gem +0 -0
  38. data/spec/envelope_spec.rb +18 -9
  39. data/spec/gentleman_spec.rb +14 -3
  40. data/spec/mails_spec.rb +2 -1
  41. data/spec/server_spec.rb +4 -4
  42. data/spec/service_cache_spec.rb +8 -0
  43. data/spec/spec_helper.rb +9 -2
  44. metadata +30 -22
@@ -0,0 +1,88 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <title>Top Level Namespace</title>
7
+ <link rel="stylesheet" href="css/style.css" type="text/css" media="screen" charset="utf-8" />
8
+ <link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
9
+
10
+ <script type="text/javascript" charset="utf-8">
11
+ relpath = '';
12
+ if (relpath != '') relpath += '/';
13
+ </script>
14
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
15
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
16
+
17
+ </head>
18
+ <body>
19
+ <script type="text/javascript" charset="utf-8">
20
+ if (window.top.frames.main) document.body.className = 'frames';
21
+ </script>
22
+
23
+ <div id="header">
24
+ <div id="menu">
25
+
26
+ <a href="_index.html">Index</a> &raquo;
27
+
28
+
29
+ <span class="title">Top Level Namespace</span>
30
+
31
+
32
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
33
+ </div>
34
+
35
+ <div id="search">
36
+ <a id="class_list_link" href="#">Class List</a>
37
+ <a id="method_list_link" href="#">Method List</a>
38
+ <a id ="file_list_link" href="#">File List</a>
39
+ </div>
40
+
41
+ <div class="clear"></div>
42
+ </div>
43
+
44
+ <iframe id="search_frame"></iframe>
45
+
46
+ <div id="content"><h1>Top Level Namespace
47
+
48
+
49
+
50
+ </h1>
51
+
52
+ <dl class="box">
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+ </dl>
62
+ <div class="clear"></div>
63
+
64
+ <h2>Defined Under Namespace</h2>
65
+ <p class="children">
66
+
67
+
68
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="MarilynRPC.html" title="MarilynRPC (module)">MarilynRPC</a></span>
69
+
70
+
71
+
72
+
73
+ </p>
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <div id="footer">
82
+ Generated on Wed Jun 8 18:26:47 2011 by
83
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
84
+ 0.6.8 (ruby-1.8.7).
85
+ </div>
86
+
87
+ </body>
88
+ </html>
@@ -4,7 +4,7 @@ require 'thread'
4
4
  module MarilynRPC
5
5
  # A class with nothing but `__send__` and `__id__`
6
6
  class ClientBlankSlate
7
- instance_methods.each { |m| undef_method m unless m =~ /^__/ }
7
+ instance_methods.each { |m| undef_method m unless m =~ /^__|object_id/ }
8
8
  end
9
9
 
10
10
  class NativeClientProxy < ClientBlankSlate
@@ -32,29 +32,31 @@ module MarilynRPC
32
32
  # TestService.time.to_f
33
33
  #
34
34
  class NativeClient
35
+ MAIL_KEY = :_mlynml
36
+
35
37
  # Create a native client for the socket.
36
38
  # @param [Socket] socket the socket to manage
37
39
  def initialize(socket)
38
40
  @socket = socket
39
41
  @semaphore = Mutex.new
40
42
  @threads = {}
41
- @responses = {}
42
43
  @thread = Thread.new do
44
+ # read the answer of the server back in
45
+ envelope = MarilynRPC::Envelope.new
43
46
  loop do
44
- # read the answer of the server back in
45
- answer = MarilynRPC::Envelope.new
46
47
  # read the header to have the size
47
- answer.parse!(@socket.read(4))
48
- # so now that we know the site, read the rest of the envelope
49
- answer.parse!(@socket.read(answer.size))
48
+ envelope.parse_header! @socket.read(MarilynRPC::Envelope::HEADER_SIZE)
49
+ # so now that we know the site, read the rest of the envelope without
50
+ # parsing
51
+ envelope.content = @socket.read(envelope.size)
50
52
 
51
53
  # returns the result part of the mail or raise the exception if there is
52
54
  # one
53
- mail = MarilynRPC::MailFactory.unpack(answer)
54
- @semaphore.synchronize do
55
- @responses[mail.tag] = mail # save the mail for the waiting thread
56
- @threads.delete(mail.tag).wakeup # wake up the waiting thread
57
- end
55
+ mail = MarilynRPC::MailFactory.unpack(envelope)
56
+ thread = @semaphore.synchronize { @threads.delete(mail.tag) }
57
+ thread[MAIL_KEY] = mail # save the mail for the waiting thread
58
+ thread.wakeup # wake up the waiting thread
59
+ envelope.reset!
58
60
  end
59
61
  end
60
62
  end
@@ -129,23 +131,20 @@ module MarilynRPC
129
131
  thread = Thread.current
130
132
  tag = "#{Time.now.to_f}:#{thread.object_id}"
131
133
 
132
- @semaphore.synchronize do
134
+ @semaphore.synchronize {
133
135
  # since this client can't multiplex, we set the tag to nil
134
136
  @socket.write(MarilynRPC::MailFactory.build_call(tag, path, method, args))
135
- end
136
-
137
- # lets write our self to the list of waining threads
138
- @semaphore.synchronize { @threads[tag] = thread }
139
-
140
- # enable the listening for a response from the remote end
141
- @thread.wakeup
137
+
138
+ # lets write our self to the list of waining threads
139
+ @threads[tag] = thread
140
+ }
142
141
 
143
142
  # stop the current thread, the thread will be started after the response
144
143
  # arrived
145
144
  Thread.stop
146
145
 
147
146
  # get mail from responses
148
- mail = @semaphore.synchronize { @responses.delete(tag) }
147
+ mail = thread[MAIL_KEY]
149
148
 
150
149
  if mail.is_a? MarilynRPC::CallResponseMail
151
150
  mail.result
@@ -1,13 +1,26 @@
1
1
  # This class handles the envelope parsing and encoding which will be used by the
2
2
  # server to handle multiple writes into the envelope.
3
3
  class MarilynRPC::Envelope
4
+ HEADER_SIZE = 5
5
+ HEADER_ENCODING = "NC".freeze
6
+
4
7
  # size of the envelope content
5
8
  attr_reader :size
6
9
 
10
+ # the type of mail that is in the envelope
11
+ attr_accessor :type
12
+
7
13
  # create a new envelope instance
8
14
  # @param [String] content the content of the new envelope
9
- def initialize(content = nil)
15
+ # @param [String] type the type of content
16
+ def initialize(content = nil, type = nil)
10
17
  self.content = content
18
+ @type = type
19
+ end
20
+
21
+ # resets the envelope object to contain no data, like if it was newly created
22
+ def reset!
23
+ @buffer, @size, @type = "", nil, nil
11
24
  end
12
25
 
13
26
  # parses the passed data
@@ -17,20 +30,24 @@ class MarilynRPC::Envelope
17
30
  # In case there are no data it will return nil.
18
31
  def parse!(data)
19
32
  @buffer += data
20
- overhang = nil
21
33
 
22
34
  # parse the length field of the
23
- if @size.nil? && @buffer.size >= 4
24
- # extract 4 bytes length header
25
- @size = @buffer.slice!(0...4).unpack("N").first
35
+ if @size == nil && @buffer.size >= HEADER_SIZE
36
+ parse_header!(@buffer.slice!(0...HEADER_SIZE))
26
37
  end
27
38
 
28
39
  # envelope is complete and contains overhang
29
- if !@size.nil? && @buffer.size > @size
30
- overhang = @buffer.slice!(@size, @buffer.size)
40
+ if @size && @buffer.size > @size
41
+ return @buffer.slice!(@size, @buffer.size) # returns the overhang
31
42
  end
32
-
33
- overhang
43
+ end
44
+
45
+ # parses the header without having much checking overhead. This is useful in
46
+ # situations where we can assure the size beforehand
47
+ # @param [String] header the header byte string of size {HEADER_SIZE}
48
+ # @api private
49
+ def parse_header!(header)
50
+ @size, @type = header.unpack(HEADER_ENCODING)
34
51
  end
35
52
 
36
53
  # returns the content of the envelope
@@ -51,7 +68,7 @@ class MarilynRPC::Envelope
51
68
  # encodes the envelope to be send over the wire
52
69
  # @return [String] encoded envelope
53
70
  def encode
54
- [@size].pack("N") + @buffer
71
+ [@size, @type].pack(HEADER_ENCODING) + @buffer
55
72
  end
56
73
 
57
74
  # checks if the complete envelope was allready parsed
@@ -54,7 +54,14 @@ class MarilynRPC::Gentleman
54
54
  # @api private
55
55
  def handle(*args)
56
56
  mail = MarilynRPC::CallResponseMail.new(self.tag, self.callback.call(*args))
57
- connection.send_mail(mail)
57
+ data = MarilynRPC::Envelope.new(mail.encode,
58
+ MarilynRPC::CallResponseMail::TYPE).encode
59
+ connection.send_data(data)
60
+ rescue Exception => exception
61
+ mail = MarilynRPC::ExceptionMail.new(self.tag, exception)
62
+ data = MarilynRPC::Envelope.new(mail.encode,
63
+ MarilynRPC::ExceptionMail::TYPE).encode
64
+ connection.send_data(data)
58
65
  end
59
66
 
60
67
  # The helper that will be called by the deferable to call
@@ -1,101 +1,86 @@
1
1
  module MarilynRPC
2
2
  # Helper that gets mixed into the mail classes to make common things easyer
3
3
  module MailHelper
4
- # generate a new serialize id which can be used in a mail
5
- # @param [Integer] a number between 0 and 254
6
- # @param [String] returns an 1 byte string as type id
7
- def self.type(nr)
8
- [nr].pack("c")
9
- end
10
-
11
- # extracts the real data and ignores the type information
12
- # @param [String] data the data to extract the mail from
13
- # @return [String] the extracted data
14
- def only_data(data)
15
- data.slice(1, data.size)
16
- end
17
-
18
- # serialize the data using marilyns default serializer
19
- # @param [Object] data the data to encode
20
- # @param [String] the serialized data
21
- def serialize(data)
22
- Marshal.dump(data)
23
- end
24
-
25
- # deserializes the passed data to the original objects
26
- # @param [String] data the serialized data
27
- # @return [Object] the deserialized object
28
- def deserialize(data)
29
- Marshal.load(data)
30
- end
4
+ SERIALIZER = Marshal
31
5
  end
32
6
 
33
7
  class CallRequestMail < Struct.new(:tag, :path, :method, :args)
34
8
  include MarilynRPC::MailHelper
35
- TYPE = MarilynRPC::MailHelper.type(1)
9
+ TYPE = 1
36
10
 
37
11
  def encode
38
- TYPE + serialize([self.tag, self.path, self.method, self.args])
12
+ SERIALIZER.dump([self.tag, self.path, self.method, self.args])
39
13
  end
40
14
 
41
15
  def decode(data)
42
- self.tag, self.path, self.method, self.args = deserialize(only_data(data))
16
+ self.tag, self.path, self.method, self.args = SERIALIZER.load(data)
43
17
  end
44
18
  end
45
19
 
46
20
  class CallResponseMail < Struct.new(:tag, :result)
47
21
  include MarilynRPC::MailHelper
48
- TYPE = MarilynRPC::MailHelper.type(2)
22
+ TYPE = 2
49
23
 
50
24
  def encode
51
- TYPE + serialize([self.tag, self.result])
25
+ SERIALIZER.dump([self.tag, self.result])
52
26
  end
53
27
 
54
28
  def decode(data)
55
- self.tag, self.result = deserialize(only_data(data))
29
+ self.tag, self.result = SERIALIZER.load(data)
56
30
  end
57
31
  end
58
32
 
59
33
  class ExceptionMail < Struct.new(:tag, :exception)
60
34
  include MarilynRPC::MailHelper
61
- TYPE = MarilynRPC::MailHelper.type(3)
35
+ TYPE = 3
62
36
 
63
37
  def encode
64
- TYPE + serialize([self.tag, self.exception])
38
+ SERIALIZER.dump([self.tag, self.exception])
65
39
  end
66
40
 
67
41
  def decode(data)
68
- self.tag, self.exception = deserialize(only_data(data))
42
+ self.tag, self.exception = SERIALIZER.load(data)
69
43
  end
70
44
  end
71
45
 
72
46
  # Helper to destiguish between the different mails
73
47
  module MailFactory
48
+ include MarilynRPC::MailHelper
49
+
50
+ # table which contains all types that can be unpacked
51
+ TYPE_LOOK_UP = {
52
+ MarilynRPC::CallRequestMail::TYPE => MarilynRPC::CallRequestMail,
53
+ MarilynRPC::CallResponseMail::TYPE => MarilynRPC::CallResponseMail,
54
+ MarilynRPC::ExceptionMail::TYPE => MarilynRPC::ExceptionMail
55
+ }
56
+
74
57
  # Parses the envelop and generate the correct mail.
75
58
  # @param [MarilynRPC::Envelope] envelope the envelope which contains a mail
76
59
  # @return [MarilynRPC::CallRequestMail, MarilynRPC::CallResponseMail,
77
60
  # MarilynRPC::ExceptionMail] the mail object that was extracted
78
- def self.unpack(envelope)
79
- data = envelope.content
80
- type = data.slice(0, 1)
81
- case type
82
- when MarilynRPC::CallRequestMail::TYPE
83
- mail = MarilynRPC::CallRequestMail.new
84
- when MarilynRPC::CallResponseMail::TYPE
85
- mail = MarilynRPC::CallResponseMail.new
86
- when MarilynRPC::ExceptionMail::TYPE
87
- mail = MarilynRPC::ExceptionMail.new
88
- else
89
- raise MarilynRPC::BrokenEnvelopeError.new("The passed envelope is broken!")
90
- end
91
- mail.decode(data)
92
- mail
61
+ def self.unpack(envelope)
62
+ if mail_klass = TYPE_LOOK_UP[envelope.type]
63
+ mail = mail_klass.new(*SERIALIZER.load(envelope.content))
64
+ else
65
+ raise MarilynRPC::BrokenEnvelopeError.new \
66
+ "The passed envelope is broken, no (correct) type!"
67
+ end
93
68
  end
94
69
 
95
- # builds the binary data for a method call
96
- def self.build_call(tag, path, method_name, args)
97
- mail = MarilynRPC::CallRequestMail.new(tag, path, method_name, args)
98
- MarilynRPC::Envelope.new(mail.encode).encode
70
+ # builds the binary data for a method call, it inlines some of the packing
71
+ # for performance critical applications.
72
+ # @param [Object] tag the tag for the object is relevate for multuplexing,
73
+ # it should be unique on a per conncetion base
74
+ # @param [Object] path the path to identifiy the service
75
+ # @param [Symbol, String] method the method name to call on the service
76
+ # @param [Array<Object>] args the arguments that are passed to the remote
77
+ # side
78
+ # @return [Object] the result of the call
79
+ def self.build_call(tag, path, method, args)
80
+ data = MarilynRPC::MailHelper::SERIALIZER.dump([tag, path, method, args])
81
+ [
82
+ data.size, MarilynRPC::CallRequestMail::TYPE
83
+ ].pack(MarilynRPC::Envelope::HEADER_ENCODING) + data
99
84
  end
100
85
  end
101
86
  end
@@ -36,25 +36,18 @@ module MarilynRPC::Server
36
36
  if @envelope.finished?
37
37
  # grep the request
38
38
  answer = @cache.call(@envelope)
39
- if answer.is_a? MarilynRPC::Gentleman
39
+ if answer.is_a? String
40
+ send_data(answer)
41
+ else
40
42
  answer.connection = self # pass connection for async responses
41
- else
42
- send_mail(answer)
43
43
  end
44
44
 
45
45
  # initialize the next envelope
46
- @envelope = MarilynRPC::Envelope.new
46
+ @envelope.reset!
47
47
  receive_data(overhang) if overhang # reenter the data loop
48
48
  end
49
49
  end
50
50
 
51
- # Send a response mail back on the wire of buffer
52
- # @param [MarilynRPC::ExceptionMail, MarilynRPC::CallResponseMail] mail the
53
- # mail that should be send to the client
54
- def send_mail(mail)
55
- send_data(MarilynRPC::Envelope.new(mail.encode).encode)
56
- end
57
-
58
51
  # Handler for client disconnect
59
52
  def unbind
60
53
  @cache.disconnect!
@@ -1,6 +1,6 @@
1
1
  # A class with nothing but `__send__`, `__id__`, `class` and `public_methods`.
2
2
  class ServiceBlankSlate
3
- instance_methods.each { |m| undef_method m unless m =~ /^__|public_methods|class/ }
3
+ instance_methods.each { |m| undef_method m unless m =~ /^__|public_methods|class|object_id/ }
4
4
  end
5
5
 
6
6
  # This class represents the base for all events, it is used for registering
@@ -36,18 +36,21 @@ class MarilynRPC::ServiceCache
36
36
  if result.is_a? MarilynRPC::Gentleman
37
37
  result.tag = tag # set the correct mail tag for the answer
38
38
  result
39
- else
40
- MarilynRPC::CallResponseMail.new(tag, result) # direct response
39
+ else # direct response
40
+ MarilynRPC::Envelope.new(MarilynRPC::CallResponseMail.new(tag, result).encode,
41
+ MarilynRPC::CallResponseMail::TYPE).encode
41
42
  end
42
43
  else
43
44
  raise MarilynRPC::BrokenEnvelopeError.new("Expected CallRequestMail Object!")
44
45
  end
45
46
  rescue MarilynRPC::BrokenEnvelopeError => exception
46
- MarilynRPC::ExceptionMail.new(nil, exception)
47
+ MarilynRPC::Envelope.new(MarilynRPC::ExceptionMail.new(nil, exception).encode,
48
+ MarilynRPC::ExceptionMail::TYPE).encode
47
49
  rescue => exception
48
50
  #puts exception
49
51
  #puts exception.backtrace.join("\n ")
50
- MarilynRPC::ExceptionMail.new(tag, exception)
52
+ MarilynRPC::Envelope.new(MarilynRPC::ExceptionMail.new(tag, exception).encode,
53
+ MarilynRPC::ExceptionMail::TYPE).encode
51
54
  end
52
55
 
53
56
  # get the service from the cache or the service registry