msgpack-rpc 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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