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.
- 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
|