marilyn-rpc 0.0.3 → 0.0.4

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