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.
- data/benchmark/client.rb +42 -14
- data/doc/MarilynRPC.html +108 -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/_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/lib/marilyn-rpc/client.rb +20 -21
- data/lib/marilyn-rpc/envelope.rb +27 -10
- data/lib/marilyn-rpc/gentleman.rb +8 -1
- data/lib/marilyn-rpc/mails.rb +40 -55
- data/lib/marilyn-rpc/server.rb +4 -11
- data/lib/marilyn-rpc/service.rb +1 -1
- data/lib/marilyn-rpc/service_cache.rb +7 -4
- data/lib/marilyn-rpc/version.rb +1 -1
- data/marilyn-rpc-0.0.2.gem +0 -0
- data/spec/envelope_spec.rb +18 -9
- data/spec/gentleman_spec.rb +14 -3
- data/spec/mails_spec.rb +2 -1
- data/spec/server_spec.rb +4 -4
- data/spec/service_cache_spec.rb +8 -0
- data/spec/spec_helper.rb +9 -2
- 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> »
|
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>
|
data/lib/marilyn-rpc/client.rb
CHANGED
@@ -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
|
-
|
48
|
-
# so now that we know the site, read the rest of the envelope
|
49
|
-
|
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(
|
54
|
-
@semaphore.synchronize
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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 =
|
147
|
+
mail = thread[MAIL_KEY]
|
149
148
|
|
150
149
|
if mail.is_a? MarilynRPC::CallResponseMail
|
151
150
|
mail.result
|
data/lib/marilyn-rpc/envelope.rb
CHANGED
@@ -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
|
-
|
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
|
24
|
-
|
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
|
30
|
-
|
40
|
+
if @size && @buffer.size > @size
|
41
|
+
return @buffer.slice!(@size, @buffer.size) # returns the overhang
|
31
42
|
end
|
32
|
-
|
33
|
-
|
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(
|
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
|
-
|
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
|
data/lib/marilyn-rpc/mails.rb
CHANGED
@@ -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
|
-
|
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 =
|
9
|
+
TYPE = 1
|
36
10
|
|
37
11
|
def encode
|
38
|
-
|
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 =
|
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 =
|
22
|
+
TYPE = 2
|
49
23
|
|
50
24
|
def encode
|
51
|
-
|
25
|
+
SERIALIZER.dump([self.tag, self.result])
|
52
26
|
end
|
53
27
|
|
54
28
|
def decode(data)
|
55
|
-
self.tag, self.result =
|
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 =
|
35
|
+
TYPE = 3
|
62
36
|
|
63
37
|
def encode
|
64
|
-
|
38
|
+
SERIALIZER.dump([self.tag, self.exception])
|
65
39
|
end
|
66
40
|
|
67
41
|
def decode(data)
|
68
|
-
self.tag, self.exception =
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
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
|
data/lib/marilyn-rpc/server.rb
CHANGED
@@ -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?
|
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
|
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!
|
data/lib/marilyn-rpc/service.rb
CHANGED
@@ -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)
|
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
|