msgpack-rpc 0.4.0 → 0.4.1
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/ChangeLog +17 -0
- data/lib/msgpack/rpc.rb +98 -13
- data/lib/msgpack/rpc/client.rb +2 -2
- data/lib/msgpack/rpc/dispatcher.rb +9 -71
- data/lib/msgpack/rpc/exception.rb +169 -11
- data/lib/msgpack/rpc/exception.rb.old +303 -0
- data/lib/msgpack/rpc/future.rb +16 -13
- data/lib/msgpack/rpc/multi_future.rb +190 -0
- data/lib/msgpack/rpc/server.rb +106 -5
- data/lib/msgpack/rpc/session.rb +51 -11
- data/lib/msgpack/rpc/transport/tcp.rb +12 -3
- data/lib/msgpack/rpc/transport/udp.rb +9 -3
- data/lib/msgpack/rpc/transport/unix.rb +12 -3
- data/lib/msgpack/rpc/version.rb +7 -0
- data/test/msgpack_rpc_test.rb +0 -1
- data/test/test_helper.rb +4 -0
- metadata +48 -21
data/ChangeLog
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
2010-08-27 version 0.4.1
|
3
|
+
|
4
|
+
* Adds MultiFuture class
|
5
|
+
* New exception mechanism
|
6
|
+
* Rescues all errors on_readable not to stop event loop
|
7
|
+
* Future: doesn't wrap callback_handler but check on calling for backward compatibility
|
8
|
+
* Responder: adds a guard not to send results twice
|
9
|
+
* Session: adds call_apply and notify_apply
|
10
|
+
* Uses jeweler and Rakefile for packaging
|
11
|
+
|
12
|
+
2010-05-28 version 0.4.0
|
13
|
+
|
14
|
+
* updates dispatch mechanism
|
15
|
+
* adds Session#call_apply and notify_apply
|
16
|
+
* Responder prevents sending results twice
|
17
|
+
|
data/lib/msgpack/rpc.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# MessagePack-RPC for Ruby
|
2
|
+
# MessagePack-RPC for Ruby
|
3
3
|
#
|
4
4
|
# Copyright (C) 2010 FURUHASHI Sadayuki
|
5
5
|
#
|
@@ -18,50 +18,114 @@
|
|
18
18
|
module MessagePack #:nodoc:
|
19
19
|
|
20
20
|
# MessagePack-RPC is an inter-process messaging library that uses
|
21
|
-
# MessagePack for object serialization.
|
22
|
-
# providing fast and scalable messaging system
|
23
|
-
# and cluster applications.
|
21
|
+
# MessagePack[http://msgpack.sourceforge.net/] for object serialization.
|
22
|
+
# The goal of the project is providing fast and scalable messaging system
|
23
|
+
# for server, client and cluster applications.
|
24
|
+
#
|
25
|
+
# You can install MessagePack-RPC for Ruby using RubyGems.
|
26
|
+
#
|
27
|
+
# gem install msgpack-rpc
|
28
|
+
#
|
24
29
|
#
|
25
30
|
# == Client API
|
26
31
|
#
|
27
32
|
# MessagePack::RPC::Client and MessagePack::RPC::SessionPool are for RPC clients.
|
28
33
|
#
|
34
|
+
#
|
29
35
|
# === Simple usage
|
36
|
+
#
|
30
37
|
# Client is subclass of Session. Use Session#call method to call remote methods.
|
31
38
|
#
|
32
|
-
# require 'msgpack/rpc'
|
39
|
+
# require 'msgpack/rpc'
|
33
40
|
#
|
34
41
|
# client = MessagePack::RPC::Client.new('127.0.0.1', 18800)
|
35
42
|
#
|
36
43
|
# result = client.call(:methodName, arg1, arg2, arg3)
|
37
44
|
#
|
45
|
+
# # ---------- server
|
46
|
+
# # ^ |
|
47
|
+
# # | |
|
48
|
+
# # ---+ +----- client
|
49
|
+
# # call join
|
50
|
+
#
|
51
|
+
#
|
38
52
|
# === Asynchronous call
|
53
|
+
#
|
39
54
|
# Use Session#call_async method to call remote methods asynchronously. It returns a Future. Use Future#get or Future#attach_callback to get actual result.
|
40
55
|
#
|
41
|
-
# require 'msgpack/rpc'
|
56
|
+
# require 'msgpack/rpc'
|
42
57
|
#
|
43
58
|
# client = MessagePack::RPC::Client.new('127.0.0.1', 18800)
|
44
59
|
#
|
45
|
-
#
|
46
|
-
#
|
60
|
+
# # call two methods concurrently
|
61
|
+
# future1 = client.call_async(:method1, arg1)
|
62
|
+
# future2 = client.call_async(:method2, arg1)
|
63
|
+
#
|
64
|
+
# # join the results
|
65
|
+
# result1 = future1.get
|
66
|
+
# result2 = future2.get
|
67
|
+
#
|
68
|
+
# # ------------------ server
|
69
|
+
# # ^ |
|
70
|
+
# # | ---------|-------- server
|
71
|
+
# # | ^ | |
|
72
|
+
# # | | | |
|
73
|
+
# # ---+-------+----- +-------+----- client
|
74
|
+
# # call call join join
|
75
|
+
#
|
76
|
+
# === Asynchronous call with multiple servers
|
77
|
+
#
|
78
|
+
# Loop enables you to invoke multiple asynchronous calls for multiple servers concurrently.
|
79
|
+
# This is good for advanced network applications.
|
80
|
+
#
|
81
|
+
# require 'msgpack/rpc'
|
82
|
+
#
|
83
|
+
# # create a event loop
|
84
|
+
# loop = MessagePack::RPC::Loop.new
|
85
|
+
#
|
86
|
+
# # connect to multiple servers
|
87
|
+
# client1 = MessagePack::RPC::Client.new('127.0.0.1', 18801, loop)
|
88
|
+
# client2 = MessagePack::RPC::Client.new('127.0.0.1', 18802, loop)
|
89
|
+
#
|
90
|
+
# # call two methods concurrently
|
91
|
+
# future1 = client1.call_async(:method1, arg1)
|
92
|
+
# future2 = client2.call_async(:method2, arg1)
|
47
93
|
#
|
94
|
+
# # join the results
|
48
95
|
# result1 = future1.get
|
49
96
|
# result2 = future2.get
|
50
97
|
#
|
98
|
+
# # ------------------ server-1 --- different servers
|
99
|
+
# # ^ | /
|
100
|
+
# # | ---------|-------- server-2
|
101
|
+
# # | ^ | |
|
102
|
+
# # | | | |
|
103
|
+
# # ---+-------+----- +-------+----- client
|
104
|
+
# # call call join join
|
105
|
+
#
|
51
106
|
# === Connection pooling
|
52
107
|
#
|
53
108
|
# SessionPool#get_session returns a Session. It pools created session and enables you to reuse established connections.
|
54
109
|
#
|
110
|
+
# require 'msgpack/rpc'
|
111
|
+
#
|
112
|
+
# sp = MessagePack::RPC::SessionPool.new
|
113
|
+
#
|
114
|
+
# client = sp.get_session('127.0.0.1', 18800)
|
115
|
+
#
|
116
|
+
# result = client.call(:methodName, arg1, arg2, arg3)
|
117
|
+
#
|
55
118
|
#
|
56
119
|
# == Server API
|
57
120
|
#
|
58
121
|
# MessagePack::RPC::Server is for RPC servers.
|
59
122
|
#
|
123
|
+
#
|
60
124
|
# === Simple usage
|
61
125
|
#
|
62
|
-
# The public methods of handler class becomes callbale.
|
126
|
+
# The public methods of the handler class becomes callbale.
|
63
127
|
#
|
64
|
-
# require 'msgpack/rpc'
|
128
|
+
# require 'msgpack/rpc'
|
65
129
|
#
|
66
130
|
# class MyHandler
|
67
131
|
# def methodName(arg1, arg2, arg3)
|
@@ -74,9 +138,10 @@ module MessagePack #:nodoc:
|
|
74
138
|
# server.listen('0.0.0.0', 18800, MyHandler.new)
|
75
139
|
# server.run
|
76
140
|
#
|
141
|
+
#
|
77
142
|
# === Advance return
|
78
143
|
#
|
79
|
-
#
|
144
|
+
# You can use *yield* to send the result without returning.
|
80
145
|
#
|
81
146
|
# class MyHandler
|
82
147
|
# def method1(arg1)
|
@@ -85,6 +150,7 @@ module MessagePack #:nodoc:
|
|
85
150
|
# end
|
86
151
|
# end
|
87
152
|
#
|
153
|
+
#
|
88
154
|
# === Delayed return
|
89
155
|
#
|
90
156
|
# You can use AsyncResult to return results later.
|
@@ -109,24 +175,39 @@ module MessagePack #:nodoc:
|
|
109
175
|
#
|
110
176
|
# You can use UDP and UNIX domain sockets instead of TCP.
|
111
177
|
#
|
178
|
+
#
|
112
179
|
# === For clients
|
113
180
|
#
|
114
181
|
# For clients, use MessagePack::RPC::UDPTransport or MessagePack::RPC::UNIXTransport.
|
115
182
|
#
|
116
|
-
# require 'msgpack/rpc'
|
183
|
+
# require 'msgpack/rpc'
|
117
184
|
# require 'msgpack/rpc/transport/udp'
|
118
185
|
#
|
119
186
|
# transport = MessagePack::RPC::UDPTransport.new
|
120
187
|
# address = MessagePack::RPC::Address.new('127.0.0.1', 18800)
|
188
|
+
#
|
121
189
|
# client = MessagePack::RPC::Client.new(transport, address)
|
122
190
|
#
|
123
191
|
# result = client.call(:methodName, arg1, arg2, arg3)
|
124
192
|
#
|
193
|
+
# You can use transports for SessionPool.
|
194
|
+
#
|
195
|
+
# require 'msgpack/rpc'
|
196
|
+
# require 'msgpack/rpc/transport/udp'
|
197
|
+
#
|
198
|
+
# transport = MessagePack::RPC::UDPTransport.new
|
199
|
+
#
|
200
|
+
# sp = MessagePack::RPC::SessionPool.new(transport)
|
201
|
+
#
|
202
|
+
# client = sp.get_session('127.0.0.1', 18800)
|
203
|
+
#
|
204
|
+
# result = client.call(:methodName, arg1, arg2, arg3)
|
205
|
+
#
|
125
206
|
# === For servers
|
126
207
|
#
|
127
208
|
# For servers, use MessagePack::RPC::UDPServerTransport or MessagePack::RPC::UNIXServerTransport.
|
128
209
|
#
|
129
|
-
# require 'msgpack/rpc'
|
210
|
+
# require 'msgpack/rpc'
|
130
211
|
# require 'msgpack/rpc/transport/udp'
|
131
212
|
#
|
132
213
|
# class MyHandler
|
@@ -138,6 +219,7 @@ module MessagePack #:nodoc:
|
|
138
219
|
#
|
139
220
|
# address = MessagePack::RPC::Address.new('0.0.0.0', 18800)
|
140
221
|
# listener = MessagePack::RPC::UDPServerTransport.new(address)
|
222
|
+
#
|
141
223
|
# server = MessagePack::RPC::Server.new
|
142
224
|
# server.listen(listener, MyHandler.new)
|
143
225
|
# server.run
|
@@ -152,11 +234,13 @@ end # module MessagePack
|
|
152
234
|
require 'msgpack'
|
153
235
|
require 'socket'
|
154
236
|
require 'rev'
|
237
|
+
require 'msgpack/rpc/version'
|
155
238
|
require 'msgpack/rpc/address'
|
156
239
|
require 'msgpack/rpc/message'
|
157
240
|
require 'msgpack/rpc/exception'
|
158
241
|
require 'msgpack/rpc/loop'
|
159
242
|
require 'msgpack/rpc/future'
|
243
|
+
require 'msgpack/rpc/multi_future'
|
160
244
|
require 'msgpack/rpc/session'
|
161
245
|
require 'msgpack/rpc/session_pool'
|
162
246
|
require 'msgpack/rpc/dispatcher'
|
@@ -164,3 +248,4 @@ require 'msgpack/rpc/client'
|
|
164
248
|
require 'msgpack/rpc/server'
|
165
249
|
require 'msgpack/rpc/transport/base'
|
166
250
|
require 'msgpack/rpc/transport/tcp'
|
251
|
+
|
data/lib/msgpack/rpc/client.rb
CHANGED
@@ -20,15 +20,15 @@ module RPC
|
|
20
20
|
|
21
21
|
|
22
22
|
# Client is usable for RPC client.
|
23
|
-
# Note that
|
23
|
+
# Note that Client includes LoopUtil.
|
24
24
|
class Client < Session
|
25
25
|
# 1. initialize(builder, address, loop = Loop.new)
|
26
26
|
# 2. initialize(host, port, loop = Loop.new)
|
27
27
|
#
|
28
28
|
# Creates a client.
|
29
29
|
def initialize(arg1, arg2, arg3=nil)
|
30
|
-
# 1.
|
31
30
|
if arg1.respond_to?(:build_transport)
|
31
|
+
# 1.
|
32
32
|
builder = arg1
|
33
33
|
address = arg2
|
34
34
|
loop = arg3 || Loop.new
|
@@ -19,86 +19,19 @@ module MessagePack
|
|
19
19
|
module RPC
|
20
20
|
|
21
21
|
|
22
|
-
|
23
|
-
def initialize
|
24
|
-
@responder = nil
|
25
|
-
@sent = false
|
26
|
-
end
|
27
|
-
|
28
|
-
def result(retval, err = nil)
|
29
|
-
unless @sent
|
30
|
-
if @responder
|
31
|
-
@responder.result(retval, err)
|
32
|
-
else
|
33
|
-
@result = [retval, err]
|
34
|
-
end
|
35
|
-
@sent = true
|
36
|
-
end
|
37
|
-
nil
|
38
|
-
end
|
39
|
-
|
40
|
-
def error(err)
|
41
|
-
result(nil, err)
|
42
|
-
nil
|
43
|
-
end
|
44
|
-
|
45
|
-
def set_responder(res) #:nodoc:
|
46
|
-
@responder = res
|
47
|
-
if @sent && @result
|
48
|
-
@responder.result(*@result)
|
49
|
-
@result = nil
|
50
|
-
end
|
51
|
-
end
|
22
|
+
module Dispatcher
|
52
23
|
end
|
53
24
|
|
54
25
|
|
55
26
|
class ObjectDispatcher
|
27
|
+
include Dispatcher
|
28
|
+
|
56
29
|
def initialize(obj, accept = obj.public_methods)
|
57
30
|
@obj = obj
|
58
31
|
@accept = accept.map {|m| m.is_a?(Integer) ? m : m.to_s }
|
59
32
|
end
|
60
33
|
|
61
|
-
def
|
62
|
-
begin
|
63
|
-
sent = false
|
64
|
-
early_result = nil
|
65
|
-
result = forward_method(session, method, param) do |result_|
|
66
|
-
unless result_.is_a?(AsyncResult)
|
67
|
-
responder.result(result_)
|
68
|
-
sent = true
|
69
|
-
end
|
70
|
-
early_result = result_
|
71
|
-
end
|
72
|
-
|
73
|
-
# FIXME on NoMethodError
|
74
|
-
# res.error(NO_METHOD_ERROR); return
|
75
|
-
|
76
|
-
# FIXME on ArgumentError
|
77
|
-
# res.error(ArgumentError); return
|
78
|
-
|
79
|
-
rescue
|
80
|
-
responder.error($!.to_s)
|
81
|
-
return
|
82
|
-
end
|
83
|
-
|
84
|
-
if early_result.is_a?(AsyncResult)
|
85
|
-
early_result.set_responder(responder)
|
86
|
-
elsif sent
|
87
|
-
return
|
88
|
-
elsif result.is_a?(AsyncResult)
|
89
|
-
result.set_responder(responder)
|
90
|
-
else
|
91
|
-
responder.result(result)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def dispatch_notify(session, method, param)
|
96
|
-
forward_method(session, method, param)
|
97
|
-
rescue
|
98
|
-
end
|
99
|
-
|
100
|
-
private
|
101
|
-
def forward_method(session, method, param, &block)
|
34
|
+
def dispatch(method, param, &block)
|
102
35
|
unless @accept.include?(method)
|
103
36
|
raise NoMethodError, "method `#{method}' is not accepted"
|
104
37
|
end
|
@@ -107,5 +40,10 @@ class ObjectDispatcher
|
|
107
40
|
end
|
108
41
|
|
109
42
|
|
43
|
+
#:nodoc:
|
44
|
+
class MethodForwarder
|
45
|
+
end
|
46
|
+
|
47
|
+
|
110
48
|
end
|
111
49
|
end
|
@@ -22,30 +22,188 @@ module RPC
|
|
22
22
|
class Error < StandardError
|
23
23
|
end
|
24
24
|
|
25
|
+
|
26
|
+
##
|
27
|
+
## MessagePack-RPC Exception
|
28
|
+
##
|
29
|
+
#
|
30
|
+
# RPCError
|
31
|
+
# |
|
32
|
+
# +-- TimeoutError
|
33
|
+
# |
|
34
|
+
# +-- TransportError
|
35
|
+
# | |
|
36
|
+
# | +-- NetworkUnreachableError
|
37
|
+
# | |
|
38
|
+
# | +-- ConnectionRefusedError
|
39
|
+
# | |
|
40
|
+
# | +-- ConnectionTimeoutError
|
41
|
+
# | |
|
42
|
+
# | +-- MalformedMessageError
|
43
|
+
# | |
|
44
|
+
# | +-- StreamClosedError
|
45
|
+
# |
|
46
|
+
# +-- CallError
|
47
|
+
# | |
|
48
|
+
# | +-- NoMethodError
|
49
|
+
# | |
|
50
|
+
# | +-- ArgumentError
|
51
|
+
# |
|
52
|
+
# +-- ServerError
|
53
|
+
# | |
|
54
|
+
# | +-- ServerBusyError
|
55
|
+
# |
|
56
|
+
# +-- RemoteError
|
57
|
+
# |
|
58
|
+
# +-- RuntimeError
|
59
|
+
# |
|
60
|
+
# +-- (user-defined errors)
|
61
|
+
|
25
62
|
class RPCError < Error
|
63
|
+
def initialize(code, *data)
|
64
|
+
@code = code.to_s
|
65
|
+
@data = data
|
66
|
+
super(@data.shift || @code)
|
67
|
+
end
|
68
|
+
attr_reader :code
|
69
|
+
attr_reader :data
|
70
|
+
|
71
|
+
def is?(code)
|
72
|
+
if code.is_a?(Class) && code < RPCError
|
73
|
+
code = code::CODE
|
74
|
+
end
|
75
|
+
@code == code || @code[0,code.length+1] == "#{code}." ||
|
76
|
+
(code == ".RemoteError" && @code[0] != ?.)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
##
|
82
|
+
# Top Level Errors
|
83
|
+
#
|
84
|
+
class TimeoutError < RPCError
|
85
|
+
CODE = ".TimeoutError"
|
86
|
+
def initialize(msg)
|
87
|
+
super(self.class::CODE, msg)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class TransportError < RPCError
|
92
|
+
CODE = ".TransportError"
|
93
|
+
def initialize(msg)
|
94
|
+
super(self.class::CODE, msg)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class CallError < RPCError
|
99
|
+
CODE = ".CallError"
|
26
100
|
def initialize(msg)
|
27
|
-
super(msg)
|
101
|
+
super(self.class::CODE, msg)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class ServerError < RPCError
|
106
|
+
CODE = ".ServerError"
|
107
|
+
def initialize(msg)
|
108
|
+
super(self.class::CODE, msg)
|
28
109
|
end
|
29
110
|
end
|
30
111
|
|
31
112
|
class RemoteError < RPCError
|
32
|
-
def initialize(
|
33
|
-
super(
|
34
|
-
@result = result
|
113
|
+
def initialize(code, *data)
|
114
|
+
super(code, *data)
|
35
115
|
end
|
36
|
-
attr_reader :result
|
37
116
|
end
|
38
117
|
|
39
|
-
|
40
|
-
|
41
|
-
|
118
|
+
|
119
|
+
##
|
120
|
+
# TransportError
|
121
|
+
#
|
122
|
+
class NetworkUnreachableError < TransportError
|
123
|
+
CODE = ".TransportError.NetworkUnreachableError"
|
124
|
+
end
|
125
|
+
|
126
|
+
class ConnectionRefusedError < TransportError
|
127
|
+
CODE = ".TransportError.ConnectionRefusedError"
|
128
|
+
end
|
129
|
+
|
130
|
+
class ConnectionTimeoutError < TransportError
|
131
|
+
CODE = ".TransportError.ConnectionTimeoutError"
|
132
|
+
end
|
133
|
+
|
134
|
+
class MalformedMessageError < TransportError
|
135
|
+
CODE = ".TransportError.ConnectionRefusedError"
|
136
|
+
end
|
137
|
+
|
138
|
+
class StreamClosedError < TransportError
|
139
|
+
CODE = ".TransportError.StreamClosedError"
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# CallError
|
144
|
+
#
|
145
|
+
class NoMethodError < CallError
|
146
|
+
CODE = ".CallError.NoMethodError"
|
147
|
+
end
|
148
|
+
|
149
|
+
class ArgumentError < CallError
|
150
|
+
CODE = ".CallError.ArgumentError"
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# ServerError
|
155
|
+
#
|
156
|
+
class ServerBusyError < ServerError
|
157
|
+
CODE = ".ServerError.ServerBusyError"
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# RuntimeError
|
162
|
+
#
|
163
|
+
class RuntimeError < RemoteError
|
164
|
+
def initialize(msg, *data)
|
165
|
+
super("RuntimeError", msg, *data)
|
42
166
|
end
|
43
167
|
end
|
44
168
|
|
45
|
-
|
46
|
-
|
47
|
-
|
169
|
+
|
170
|
+
class RPCError
|
171
|
+
def self.create(code, data)
|
172
|
+
if code[0] == ?.
|
173
|
+
code = code.dup
|
174
|
+
while true
|
175
|
+
if klass = SYSTEM_ERROR_TABLE[code]
|
176
|
+
return klass.new(*data)
|
177
|
+
end
|
178
|
+
if code.slice!(/\.[^\.]*$/) == nil || code.empty?
|
179
|
+
return RPCError.new(code, *data)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
elsif code == RuntimeError::CODE
|
184
|
+
RuntimeError.new(*data)
|
185
|
+
|
186
|
+
else
|
187
|
+
RemoteError.new(code, *data)
|
188
|
+
end
|
48
189
|
end
|
190
|
+
|
191
|
+
private
|
192
|
+
SYSTEM_ERROR_TABLE = {
|
193
|
+
TimeoutError::CODE => TimeoutError,
|
194
|
+
TransportError::CODE => TransportError,
|
195
|
+
CallError::CODE => CallError,
|
196
|
+
ServerError::CODE => ServerError,
|
197
|
+
RemoteError::CODE => RemoteError,
|
198
|
+
NetworkUnreachableError::CODE => NetworkUnreachableError,
|
199
|
+
ConnectionRefusedError::CODE => ConnectionRefusedError,
|
200
|
+
ConnectionTimeoutError::CODE => ConnectionTimeoutError,
|
201
|
+
MalformedMessageError::CODE => MalformedMessageError,
|
202
|
+
StreamClosedError::CODE => StreamClosedError,
|
203
|
+
NoMethodError::CODE => NoMethodError,
|
204
|
+
ArgumentError::CODE => ArgumentError,
|
205
|
+
ServerBusyError::CODE => ServerBusyError,
|
206
|
+
}
|
49
207
|
end
|
50
208
|
|
51
209
|
|