msgpack-rpc 0.3.0 → 0.4.0
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/NOTICE +5 -1
- data/lib/msgpack/rpc.rb +144 -1430
- data/lib/msgpack/rpc/address.rb +144 -0
- data/lib/msgpack/rpc/client.rb +80 -0
- data/lib/msgpack/rpc/dispatcher.rb +111 -0
- data/lib/msgpack/rpc/exception.rb +53 -0
- data/lib/msgpack/rpc/future.rb +116 -0
- data/lib/msgpack/rpc/loop.rb +87 -0
- data/lib/msgpack/rpc/message.rb +31 -0
- data/lib/msgpack/rpc/server.rb +111 -0
- data/lib/msgpack/rpc/session.rb +157 -0
- data/lib/msgpack/rpc/session_pool.rb +91 -0
- data/lib/msgpack/rpc/transport/base.rb +48 -0
- data/lib/msgpack/rpc/transport/tcp.rb +242 -0
- data/lib/msgpack/rpc/transport/udp.rb +192 -0
- data/lib/msgpack/rpc/transport/unix.rb +165 -0
- data/test/test_helper.rb +2 -2
- metadata +17 -3
@@ -0,0 +1,87 @@
|
|
1
|
+
#
|
2
|
+
# MessagePack-RPC for Ruby
|
3
|
+
#
|
4
|
+
# Copyright (C) 2010 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module MessagePack
|
19
|
+
module RPC
|
20
|
+
|
21
|
+
|
22
|
+
Loop = Rev::Loop
|
23
|
+
|
24
|
+
|
25
|
+
module LoopUtil
|
26
|
+
attr_reader :loop
|
27
|
+
|
28
|
+
class Timer < Rev::TimerWatcher
|
29
|
+
def initialize(interval, repeating, &block)
|
30
|
+
@block = block
|
31
|
+
super(interval, repeating)
|
32
|
+
end
|
33
|
+
def on_timer
|
34
|
+
@block.call
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def start_timer(interval, repeating, &block)
|
39
|
+
@loop.attach Timer.new(interval, repeating, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
class TaskQueue < Rev::AsyncWatcher
|
43
|
+
def initialize
|
44
|
+
@queue = []
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def push(task)
|
49
|
+
@queue.push(task)
|
50
|
+
signal
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_signal
|
54
|
+
while task = @queue.shift
|
55
|
+
begin
|
56
|
+
task.call
|
57
|
+
rescue
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def submit(task = nil, &block)
|
64
|
+
task ||= block
|
65
|
+
unless @queue
|
66
|
+
@queue = TaskQueue.new
|
67
|
+
@loop.attach(@queue)
|
68
|
+
end
|
69
|
+
@queue.push(task)
|
70
|
+
end
|
71
|
+
|
72
|
+
def run
|
73
|
+
@loop.run
|
74
|
+
end
|
75
|
+
|
76
|
+
def stop
|
77
|
+
@queue.detach if @queue && @queue.attached?
|
78
|
+
@loop.stop
|
79
|
+
# attach dummy timer
|
80
|
+
@loop.attach Rev::TimerWatcher.new(0, false)
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#
|
2
|
+
# MessagePack-RPC for Ruby
|
3
|
+
#
|
4
|
+
# Copyright (C) 2010 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module MessagePack
|
19
|
+
module RPC
|
20
|
+
|
21
|
+
|
22
|
+
REQUEST = 0 # [0, msgid, method, param]
|
23
|
+
RESPONSE = 1 # [1, msgid, error, result]
|
24
|
+
NOTIFY = 2 # [2, method, param]
|
25
|
+
|
26
|
+
NO_METHOD_ERROR = 0x01;
|
27
|
+
ARGUMENT_ERROR = 0x02;
|
28
|
+
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#
|
2
|
+
# MessagePack-RPC for Ruby
|
3
|
+
#
|
4
|
+
# Copyright (C) 2010 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module MessagePack
|
19
|
+
module RPC
|
20
|
+
|
21
|
+
|
22
|
+
# Server is usable for RPC server.
|
23
|
+
# Note that Server is a SessionPool.
|
24
|
+
class Server < SessionPool
|
25
|
+
# 1. initialize(builder, loop = Loop.new)
|
26
|
+
# 2. initialize(loop = Loop.new)
|
27
|
+
def initialize(arg1=nil, arg2=nil)
|
28
|
+
super(arg1, arg2)
|
29
|
+
@dispatcher = nil
|
30
|
+
@listeners = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def serve(obj, accept = obj.public_methods)
|
34
|
+
@dispatcher = ObjectDispatcher.new(obj, accept)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# 1. listen(listener, obj = nil, accept = obj.public_methods)
|
39
|
+
# 2. listen(host, port, obj = nil, accept = obj.public_methods)
|
40
|
+
def listen(arg1, arg2 = nil, arg3 = nil, arg4 = nil)
|
41
|
+
if arg1.respond_to?(:listen)
|
42
|
+
# 1.
|
43
|
+
listener = arg1
|
44
|
+
obj = arg2
|
45
|
+
accept = arg3 || obj.public_methods
|
46
|
+
else
|
47
|
+
# 2.
|
48
|
+
listener = TCPServerTransport.new(Address.new(arg1,arg2))
|
49
|
+
obj = arg3
|
50
|
+
accept = arg4 || obj.public_methods
|
51
|
+
end
|
52
|
+
|
53
|
+
unless obj.nil?
|
54
|
+
serve(obj, accept)
|
55
|
+
end
|
56
|
+
|
57
|
+
listener.listen(self)
|
58
|
+
@listeners.push(listener)
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def close
|
63
|
+
@listeners.reject! {|listener|
|
64
|
+
listener.close
|
65
|
+
true
|
66
|
+
}
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
# from ServerTransport
|
71
|
+
def on_request(sendable, msgid, method, param) #:nodoc:
|
72
|
+
responder = Responder.new(sendable, msgid)
|
73
|
+
@dispatcher.dispatch_request(self, method, param, responder)
|
74
|
+
end
|
75
|
+
|
76
|
+
# from ServerTransport
|
77
|
+
def on_notify(method, param) #:nodoc:
|
78
|
+
@dispatcher.dispatch_notify(self, method, param)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
class Responder
|
84
|
+
def initialize(sendable, msgid)
|
85
|
+
@sendable = sendable # send_message method is required
|
86
|
+
@msgid = msgid
|
87
|
+
end
|
88
|
+
|
89
|
+
def result(retval, err = nil)
|
90
|
+
data = [RESPONSE, @msgid, err, retval].to_msgpack
|
91
|
+
@sendable.send_data(data)
|
92
|
+
end
|
93
|
+
|
94
|
+
def error(err, retval = nil)
|
95
|
+
result(retval, err)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
#:nodoc:
|
101
|
+
class Server::Base
|
102
|
+
def initialize(*args)
|
103
|
+
@base = Server.new(*args)
|
104
|
+
@base.serve(self)
|
105
|
+
end
|
106
|
+
attr_reader :base
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
#
|
2
|
+
# MessagePack-RPC for Ruby
|
3
|
+
#
|
4
|
+
# Copyright (C) 2010 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module MessagePack
|
19
|
+
module RPC
|
20
|
+
|
21
|
+
|
22
|
+
# Session is a abstract class corresponds with a remote host.
|
23
|
+
# You can call remote method using call, call_async, callback or notify method.
|
24
|
+
class Session
|
25
|
+
def initialize(builder, address, loop)
|
26
|
+
@address = address
|
27
|
+
@loop = loop
|
28
|
+
@reqtable = {}
|
29
|
+
@timeout = 10 # FIXME default timeout time
|
30
|
+
@seqid = 0
|
31
|
+
@transport = builder.build_transport(self, address)
|
32
|
+
end
|
33
|
+
attr_reader :loop, :address
|
34
|
+
|
35
|
+
# Sets and gets timeout in seconds.
|
36
|
+
attr_accessor :timeout
|
37
|
+
|
38
|
+
# backward compatibility
|
39
|
+
def port #:nodoc:
|
40
|
+
@address.port;
|
41
|
+
end
|
42
|
+
|
43
|
+
# backward compatibility
|
44
|
+
def host #:nodoc:
|
45
|
+
@address.host;
|
46
|
+
end
|
47
|
+
|
48
|
+
# call-seq:
|
49
|
+
# call(symbol, *args) -> result of remote method
|
50
|
+
#
|
51
|
+
# Calls remote method.
|
52
|
+
# This method is same as call_async(method, *args).get
|
53
|
+
def call(method, *args)
|
54
|
+
call_async(method, *args).get
|
55
|
+
end
|
56
|
+
|
57
|
+
# call-seq:
|
58
|
+
# call_async(symbol, *args) -> Future
|
59
|
+
#
|
60
|
+
# Calls remote method asynchronously.
|
61
|
+
# This method is non-blocking and returns Future.
|
62
|
+
def call_async(method, *args)
|
63
|
+
future = send_request(method, args)
|
64
|
+
end
|
65
|
+
|
66
|
+
# backward compatibility
|
67
|
+
alias send call_async #:nodoc:
|
68
|
+
|
69
|
+
# call-seq:
|
70
|
+
# callback(symbol, *args) {|future| }
|
71
|
+
#
|
72
|
+
# Calls remote method asynchronously.
|
73
|
+
# The callback method is called with Future when the result is reached.
|
74
|
+
# This method is same as call_async(method, *args).attach_callback {|future| }
|
75
|
+
def callback(method, *args, &block)
|
76
|
+
future = send_request(method, args)
|
77
|
+
future.attach_callback(block)
|
78
|
+
future
|
79
|
+
end
|
80
|
+
|
81
|
+
# call-seq:
|
82
|
+
# notify(symbol, *args) -> nil
|
83
|
+
#
|
84
|
+
# Calls remote method with NOTIFY protocol.
|
85
|
+
# It doesn't require server to return results.
|
86
|
+
# This method is non-blocking and returns nil.
|
87
|
+
def notify(method, *args)
|
88
|
+
send_notify(method, args)
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# Closes underlaying Transport and destroy resources.
|
93
|
+
def close
|
94
|
+
@transport.close
|
95
|
+
@reqtable = {}
|
96
|
+
@seqid = 0
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
# from ClientTransport
|
101
|
+
def on_response(sock, msgid, error, result) #:nodoc:
|
102
|
+
if future = @reqtable.delete(msgid)
|
103
|
+
future.set_result(error, result)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# from ClientTransport
|
108
|
+
def on_connect_failed #:nodoc:
|
109
|
+
@reqtable.reject! {|msgid, future|
|
110
|
+
begin
|
111
|
+
future.set_result ConnectError.new, nil
|
112
|
+
rescue
|
113
|
+
end
|
114
|
+
true
|
115
|
+
}
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
|
119
|
+
# from Client, SessionPool
|
120
|
+
def step_timeout #:nodoc:
|
121
|
+
timedout = []
|
122
|
+
@reqtable.reject! {|msgid, future|
|
123
|
+
if future.step_timeout
|
124
|
+
timedout.push(future)
|
125
|
+
true
|
126
|
+
end
|
127
|
+
}
|
128
|
+
timedout.each {|future|
|
129
|
+
begin
|
130
|
+
future.set_result TimeoutError.new, nil
|
131
|
+
rescue
|
132
|
+
end
|
133
|
+
}
|
134
|
+
!@reqtable.empty?
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
def send_request(method, param)
|
139
|
+
method = method.to_s
|
140
|
+
msgid = @seqid
|
141
|
+
@seqid += 1; if @seqid >= 1<<31 then @seqid = 0 end
|
142
|
+
data = [REQUEST, msgid, method, param].to_msgpack
|
143
|
+
@transport.send_data(data)
|
144
|
+
@reqtable[msgid] = Future.new(self, @loop)
|
145
|
+
end
|
146
|
+
|
147
|
+
def send_notify(method, param)
|
148
|
+
method = method.to_s
|
149
|
+
data = [NOTIFY, method, param].to_msgpack
|
150
|
+
@transport.send_data(data)
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#
|
2
|
+
# MessagePack-RPC for Ruby
|
3
|
+
#
|
4
|
+
# Copyright (C) 2010 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module MessagePack
|
19
|
+
module RPC
|
20
|
+
|
21
|
+
|
22
|
+
# SessionPool is usable for connection pooling.
|
23
|
+
# You can get pooled Session using get_session method.
|
24
|
+
# Note that SessionPool includes LoopUtil.
|
25
|
+
class SessionPool
|
26
|
+
# 1. initialize(builder, loop = Loop.new)
|
27
|
+
# 2. initialize(loop = Loop.new)
|
28
|
+
#
|
29
|
+
# Creates an SessionPool.
|
30
|
+
def initialize(arg1=nil, arg2=nil)
|
31
|
+
if arg1.respond_to?(:build_transport)
|
32
|
+
# 1.
|
33
|
+
builder = arg1
|
34
|
+
loop = arg2 || Loop.new
|
35
|
+
else
|
36
|
+
# 2.
|
37
|
+
builder = TCPTransport.new
|
38
|
+
loop = arg1 || Loop.new
|
39
|
+
end
|
40
|
+
|
41
|
+
@builder = builder
|
42
|
+
@loop = loop
|
43
|
+
@pool = {}
|
44
|
+
|
45
|
+
@timer = Timer.new(1, true, &method(:step_timeout))
|
46
|
+
loop.attach(@timer)
|
47
|
+
end
|
48
|
+
|
49
|
+
# 1. get_session(address)
|
50
|
+
# 2. get_session(host, port)
|
51
|
+
#
|
52
|
+
# Returns pooled Session.
|
53
|
+
# If there are no pooled Session for the specified address,
|
54
|
+
# this method creates the Session and pools it.
|
55
|
+
def get_session(arg1, arg2=nil)
|
56
|
+
if arg2.nil?
|
57
|
+
# 1.
|
58
|
+
addr = arg1
|
59
|
+
else
|
60
|
+
# 2.
|
61
|
+
host = arg1
|
62
|
+
port = arg2
|
63
|
+
addr = Address.new(host, port)
|
64
|
+
end
|
65
|
+
|
66
|
+
@pool[addr] ||= Session.new(@builder, addr, @loop)
|
67
|
+
end
|
68
|
+
|
69
|
+
# backward compatibility
|
70
|
+
alias get_session_addr get_session #:nodoc:
|
71
|
+
|
72
|
+
def close
|
73
|
+
@pool.reject! {|addr, s|
|
74
|
+
s.close
|
75
|
+
true
|
76
|
+
}
|
77
|
+
@timer.detach if @timer.attached?
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
|
81
|
+
include LoopUtil
|
82
|
+
|
83
|
+
private
|
84
|
+
def step_timeout
|
85
|
+
@pool.each_pair {|addr,s| s.step_timeout }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|