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 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 TCP transport
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. The goal of the project is
22
- # providing fast and scalable messaging system for server, client
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' # gem install 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' # gem install msgpack-rpc
56
+ # require 'msgpack/rpc'
42
57
  #
43
58
  # client = MessagePack::RPC::Client.new('127.0.0.1', 18800)
44
59
  #
45
- # future1 = client.call(:method1, arg1) # call two methods concurrently
46
- # future2 = client.call(:method2, arg1)
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' # gem install 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
- # In the handler method, you can use *yield* to send the result without returning.
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' # gem install 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' # gem install 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
+
@@ -20,15 +20,15 @@ module RPC
20
20
 
21
21
 
22
22
  # Client is usable for RPC client.
23
- # Note that SessionPool includes LoopUtil.
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
- class AsyncResult
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 dispatch_request(session, method, param, responder)
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(msg, result = nil)
33
- super(msg)
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
- class TimeoutError < Error
40
- def initialize(msg = "request timed out")
41
- super
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
- class ConnectError < TimeoutError
46
- def initialize(msg = "connect failed")
47
- super
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