pomelo-citrus-rpc 0.0.2
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.
- checksums.yaml +7 -0
- data/README.md +20 -0
- data/Rakefile +0 -0
- data/citrus-rpc.gemspec +32 -0
- data/example/client.rb +43 -0
- data/example/remote/test/service.rb +9 -0
- data/example/server.rb +20 -0
- data/lib/citrus-rpc.rb +16 -0
- data/lib/citrus-rpc/rpc-client/client.rb +328 -0
- data/lib/citrus-rpc/rpc-client/mailboxes/ws_mailbox.rb +164 -0
- data/lib/citrus-rpc/rpc-client/mailstation.rb +363 -0
- data/lib/citrus-rpc/rpc-client/proxy.rb +37 -0
- data/lib/citrus-rpc/rpc-client/router.rb +63 -0
- data/lib/citrus-rpc/rpc-server/acceptors/ws_acceptor.rb +143 -0
- data/lib/citrus-rpc/rpc-server/dispatcher.rb +36 -0
- data/lib/citrus-rpc/rpc-server/gateway.rb +58 -0
- data/lib/citrus-rpc/rpc-server/server.rb +92 -0
- data/lib/citrus-rpc/util/constants.rb +20 -0
- data/lib/citrus-rpc/util/utils.rb +42 -0
- data/lib/citrus-rpc/version.rb +7 -0
- data/spec/mock-remote/area/add_one_remote.rb +13 -0
- data/spec/mock-remote/area/add_three_remote.rb +9 -0
- data/spec/mock-remote/area/who_am_i_remote.rb +9 -0
- data/spec/mock-remote/connector/who_am_i_remote.rb +9 -0
- data/spec/rpc-client/client_spec.rb +166 -0
- data/spec/rpc-client/mailstaion_spec.rb +235 -0
- data/spec/rpc-client/proxy_spec.rb +8 -0
- data/spec/rpc-client/router_spec.rb +8 -0
- data/spec/rpc-client/ws_mailbox_spec.rb +144 -0
- data/spec/rpc-server/client/mock-tcp-client.rb +6 -0
- data/spec/rpc-server/client/mock-ws-client.rb +48 -0
- data/spec/rpc-server/dispatcher_spec.rb +88 -0
- data/spec/rpc-server/gateway_spec.rb +206 -0
- data/spec/rpc-server/server_spec.rb +79 -0
- data/spec/rpc-server/ws_acceptor_spec.rb +138 -0
- data/spec/spec_helper.rb +25 -0
- metadata +179 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
# Author:: MinixLi (gmail: MinixLi1986)
|
2
|
+
# Homepage:: http://citrus.inspawn.com
|
3
|
+
# Date:: 7 July 2014
|
4
|
+
|
5
|
+
require 'websocket-eventmachine-client'
|
6
|
+
require 'citrus-rpc/util/constants'
|
7
|
+
|
8
|
+
module CitrusRpc
|
9
|
+
# RpcClient
|
10
|
+
#
|
11
|
+
#
|
12
|
+
module RpcClient
|
13
|
+
# WsMailBox
|
14
|
+
#
|
15
|
+
#
|
16
|
+
class WsMailBox
|
17
|
+
include Utils::EventEmitter
|
18
|
+
|
19
|
+
# Create a new websocket mailbox
|
20
|
+
#
|
21
|
+
# @param [Hash] server server info
|
22
|
+
# @param [Hash] args Options
|
23
|
+
#
|
24
|
+
# @option args [Object] context
|
25
|
+
# @option args [Object] route_context
|
26
|
+
# @option args [#call] router
|
27
|
+
# @option args [String] router_type
|
28
|
+
def initialize server, args={}
|
29
|
+
@cur_id = 0
|
30
|
+
|
31
|
+
@id = server[:id]
|
32
|
+
@host = server[:host]
|
33
|
+
@port = server[:port]
|
34
|
+
|
35
|
+
@requests = {}
|
36
|
+
@timeout = {}
|
37
|
+
@queue = []
|
38
|
+
|
39
|
+
@buffer_msg = args[:buffer_msg]
|
40
|
+
@interval = args[:interval] || Constants::DefaultParams::Interval
|
41
|
+
@timeout_value = args[:timeout] || Constants::DefaultParams::Timeout
|
42
|
+
|
43
|
+
@connected = false
|
44
|
+
@closed = false
|
45
|
+
|
46
|
+
@args = args
|
47
|
+
end
|
48
|
+
|
49
|
+
# Connect to remote server
|
50
|
+
def connect
|
51
|
+
if @connected
|
52
|
+
block_given? and yield Exception.new 'mailbox has already connected'
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
@ws = WebSocket::EventMachine::Client.connect :uri => 'ws://' + @host + ':' + @port.to_s
|
58
|
+
@ws.onopen {
|
59
|
+
return if @connected
|
60
|
+
@connected = true
|
61
|
+
@timer = EM.add_periodic_timer(@interval) { flush } if @buffer_msg
|
62
|
+
block_given? and yield
|
63
|
+
}
|
64
|
+
|
65
|
+
@ws.onmessage { |msg, type|
|
66
|
+
process_msg msg, type
|
67
|
+
}
|
68
|
+
|
69
|
+
@ws.onerror { |err| }
|
70
|
+
@ws.onclose { |code, reason|
|
71
|
+
emit :close, @id
|
72
|
+
}
|
73
|
+
rescue => err
|
74
|
+
block_given? and yield err
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Close the mail box
|
79
|
+
def close
|
80
|
+
return if @closed
|
81
|
+
@closed = true
|
82
|
+
@ws.close
|
83
|
+
end
|
84
|
+
|
85
|
+
# Send message to remote server
|
86
|
+
#
|
87
|
+
# @param [Hash] msg
|
88
|
+
# @param [Hash] opts
|
89
|
+
# @param [#call] block
|
90
|
+
def send msg, opts, block
|
91
|
+
unless @connected
|
92
|
+
block.call Exception.new 'websocket mailbox has not connected'
|
93
|
+
return
|
94
|
+
end
|
95
|
+
|
96
|
+
if @closed
|
97
|
+
block.call Exception.new 'websocket mailbox has already closed'
|
98
|
+
return
|
99
|
+
end
|
100
|
+
|
101
|
+
id = @cur_id
|
102
|
+
@cur_id += 1
|
103
|
+
@requests[id] = block
|
104
|
+
|
105
|
+
pkg = { :id => id, :msg => msg }
|
106
|
+
if @buffer_msg
|
107
|
+
enqueue pkg
|
108
|
+
else
|
109
|
+
@ws.send pkg.to_json
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Enqueue the package
|
116
|
+
#
|
117
|
+
# @param [Hash] pkg
|
118
|
+
#
|
119
|
+
# @private
|
120
|
+
def enqueue pkg
|
121
|
+
@queue << pkg
|
122
|
+
end
|
123
|
+
|
124
|
+
# Flush
|
125
|
+
#
|
126
|
+
# @private
|
127
|
+
def flush
|
128
|
+
if @closed || @queue.length == 0
|
129
|
+
return
|
130
|
+
end
|
131
|
+
@ws.send @queue.to_json
|
132
|
+
@queue = []
|
133
|
+
end
|
134
|
+
|
135
|
+
# Process message
|
136
|
+
#
|
137
|
+
# @param [Hash] msg
|
138
|
+
#
|
139
|
+
# @private
|
140
|
+
def process_msg msg, type
|
141
|
+
begin
|
142
|
+
pkg = JSON.parse msg
|
143
|
+
pkg_id = pkg['id']
|
144
|
+
pkg_resp = pkg['resp']
|
145
|
+
|
146
|
+
return unless block = @requests[pkg_id]
|
147
|
+
@requests.delete pkg_id
|
148
|
+
|
149
|
+
args = [nil]
|
150
|
+
pkg_resp.each { |arg| args << arg }
|
151
|
+
|
152
|
+
block.call *args
|
153
|
+
rescue => err
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Batch version for process_msg
|
158
|
+
#
|
159
|
+
# @private
|
160
|
+
def process_msgs
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,363 @@
|
|
1
|
+
# Author:: MinixLi (gmail: MinixLi1986)
|
2
|
+
# Homepage:: http://citrus.inspawn.com
|
3
|
+
# Date:: 7 July 2014
|
4
|
+
|
5
|
+
require 'citrus-rpc/rpc-client/mailboxes/ws_mailbox'
|
6
|
+
require 'citrus-rpc/util/constants'
|
7
|
+
|
8
|
+
module CitrusRpc
|
9
|
+
# RpcClient
|
10
|
+
#
|
11
|
+
#
|
12
|
+
module RpcClient
|
13
|
+
# MailStation
|
14
|
+
#
|
15
|
+
#
|
16
|
+
class MailStation
|
17
|
+
include Utils::EventEmitter
|
18
|
+
|
19
|
+
attr_reader :servers, :mailbox_class
|
20
|
+
|
21
|
+
# Create a new mail station
|
22
|
+
#
|
23
|
+
# @param [Hash] args Options
|
24
|
+
#
|
25
|
+
# @option args [Class] mailbox_class
|
26
|
+
# @option args [Integer] pending_size
|
27
|
+
def initialize args={}
|
28
|
+
@args = args
|
29
|
+
@servers = {} # [Hash] server id => info
|
30
|
+
@servers_map = {} # [Hash] server type => servers array
|
31
|
+
@onlines = {} # [Hash] server id => true or false
|
32
|
+
@mailbox_class = @args[:mailbox_class] || WsMailBox
|
33
|
+
|
34
|
+
# filters
|
35
|
+
@befores = {}
|
36
|
+
@afters = {}
|
37
|
+
|
38
|
+
# pending request queues
|
39
|
+
@pendings = {}
|
40
|
+
@pending_size = @args[:pending_size] || Constants::DefaultParams::PendingSize
|
41
|
+
|
42
|
+
# onnecting remote server mailbox map
|
43
|
+
@connecting = {}
|
44
|
+
|
45
|
+
# working mailbox map
|
46
|
+
@mailboxes = {}
|
47
|
+
|
48
|
+
@state = :state_inited
|
49
|
+
end
|
50
|
+
|
51
|
+
# Start station and connect all mailboxes to remote servers
|
52
|
+
def start
|
53
|
+
unless @state == :state_inited
|
54
|
+
block_given? and yield Exception.new 'station has started'
|
55
|
+
return
|
56
|
+
end
|
57
|
+
EM.next_tick { @state = :state_started; block_given? and yield }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Stop station and all its mailboxes
|
61
|
+
#
|
62
|
+
# @param [Boolean] force
|
63
|
+
def stop force=false
|
64
|
+
unless @state == :state_started
|
65
|
+
return
|
66
|
+
end
|
67
|
+
@state = :state_closed
|
68
|
+
|
69
|
+
close_all = Proc.new {
|
70
|
+
@mailboxes.each { |server_id, mailbox| mailbox.close }
|
71
|
+
}
|
72
|
+
if force
|
73
|
+
close_all.call
|
74
|
+
else
|
75
|
+
EM.add_timer(Constants::DefaultParams::GraceTimeout) { close_all.call }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Add a new server info into the mail station
|
80
|
+
#
|
81
|
+
# @param [Hash] server_info
|
82
|
+
def add_server server_info
|
83
|
+
return unless server_info && server_info[:id]
|
84
|
+
|
85
|
+
id = server_info[:id]
|
86
|
+
type = server_info[:server_type]
|
87
|
+
|
88
|
+
@servers[id] = server_info
|
89
|
+
@onlines[id] = true
|
90
|
+
|
91
|
+
@servers_map[type] ||= []
|
92
|
+
@servers_map[type] << id
|
93
|
+
|
94
|
+
emit :add_server, id
|
95
|
+
end
|
96
|
+
|
97
|
+
# Batch version for add new server info
|
98
|
+
#
|
99
|
+
# @param [Array] server_infos
|
100
|
+
def add_servers server_infos
|
101
|
+
return unless server_infos && server_infos.length > 0
|
102
|
+
server_infos.each { |server_info| add_server server_info }
|
103
|
+
end
|
104
|
+
|
105
|
+
# Remove a server info from the mail station and remove
|
106
|
+
# the mailbox instance associated with the server id.
|
107
|
+
#
|
108
|
+
# @param [String] id
|
109
|
+
def remove_server id
|
110
|
+
@onlines[id] = false
|
111
|
+
|
112
|
+
if @servers[id]
|
113
|
+
type = @servers[id][:server_type]
|
114
|
+
@servers_map[type].delete id
|
115
|
+
end
|
116
|
+
|
117
|
+
if mailbox = @mailboxes[id]
|
118
|
+
mailbox.close
|
119
|
+
@mailboxes.delete id
|
120
|
+
end
|
121
|
+
|
122
|
+
emit :remove_server, id
|
123
|
+
end
|
124
|
+
|
125
|
+
# Batch version for remove remote servers
|
126
|
+
#
|
127
|
+
# @param [Array] ids
|
128
|
+
def remove_servers ids
|
129
|
+
return unless ids && ids.length > 0
|
130
|
+
ids.each { |id| remove_server ids }
|
131
|
+
end
|
132
|
+
|
133
|
+
# Clear station infomation
|
134
|
+
def clear_station
|
135
|
+
@onlines = {}
|
136
|
+
@servers_map = {}
|
137
|
+
end
|
138
|
+
|
139
|
+
# Replace servers
|
140
|
+
#
|
141
|
+
# @param [Array] server_infos
|
142
|
+
def replace_servers server_infos
|
143
|
+
clear_station
|
144
|
+
return unless server_infos && server_infos.length > 0
|
145
|
+
|
146
|
+
server_infos.each { |server_info|
|
147
|
+
id = server_info[:server_id]
|
148
|
+
type = server_info[:server_type]
|
149
|
+
|
150
|
+
@onlines[id] = true
|
151
|
+
@servers[id] = server_info
|
152
|
+
|
153
|
+
@servers_map[type] ||= []
|
154
|
+
@servers_map[type] << id
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
# Dispatch rpc message to the mailbox
|
159
|
+
#
|
160
|
+
# @param [String] server_id
|
161
|
+
# @param [Hash] msg
|
162
|
+
# @param [Hash] opts
|
163
|
+
# @param [#call] block
|
164
|
+
def dispatch server_id, msg, opts, block
|
165
|
+
unless @state == :state_started
|
166
|
+
block.call Exception.new 'client is not running now'
|
167
|
+
return
|
168
|
+
end
|
169
|
+
|
170
|
+
args = [server_id, msg, opts, block]
|
171
|
+
|
172
|
+
unless @mailboxes[server_id]
|
173
|
+
# try to connect remote server if mailbox instance not exist yet
|
174
|
+
unless lazy_connect server_id, @mailbox_class
|
175
|
+
emit :error
|
176
|
+
end
|
177
|
+
# push request to the pending queue
|
178
|
+
add_to_pending server_id, args
|
179
|
+
return
|
180
|
+
end
|
181
|
+
|
182
|
+
# if the mailbox is connecting to remote server
|
183
|
+
if @connecting[server_id]
|
184
|
+
add_to_pending server_id, args
|
185
|
+
return
|
186
|
+
end
|
187
|
+
|
188
|
+
send = Proc.new { |err, server_id, msg, opts|
|
189
|
+
if err
|
190
|
+
return
|
191
|
+
end
|
192
|
+
unless mailbox = @mailboxes[server_id]
|
193
|
+
return
|
194
|
+
end
|
195
|
+
mailbox.send(msg, opts, proc{ |*args|
|
196
|
+
if send_err = args[0]
|
197
|
+
emit :error
|
198
|
+
return
|
199
|
+
end
|
200
|
+
args.shift
|
201
|
+
do_filter nil, server_id, msg, opts, @befores, 0, 'after', proc{ |err, server_id, msg, opts|
|
202
|
+
if err
|
203
|
+
end
|
204
|
+
block.call *args
|
205
|
+
}
|
206
|
+
})
|
207
|
+
}
|
208
|
+
|
209
|
+
do_filter nil, server_id, msg, opts, @afters, 0, 'before', send
|
210
|
+
end
|
211
|
+
|
212
|
+
# Add a before filter
|
213
|
+
#
|
214
|
+
# @param [#call] filter
|
215
|
+
def before filter
|
216
|
+
if filter.instance_of? Array
|
217
|
+
@befores.concat filter
|
218
|
+
return
|
219
|
+
end
|
220
|
+
@befores << filter
|
221
|
+
end
|
222
|
+
|
223
|
+
# Add after filter
|
224
|
+
#
|
225
|
+
# @param [#call] filter
|
226
|
+
def after filter
|
227
|
+
if filter.instance_of? Array
|
228
|
+
@afters.concat filter
|
229
|
+
return
|
230
|
+
end
|
231
|
+
@afters << filter
|
232
|
+
end
|
233
|
+
|
234
|
+
# Add before and after filter
|
235
|
+
#
|
236
|
+
# @param [#call] filter
|
237
|
+
def filter filter
|
238
|
+
@befores << filter
|
239
|
+
@afters << filter
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
# Try to connect to remote server
|
245
|
+
#
|
246
|
+
# @param [String] server_id
|
247
|
+
#
|
248
|
+
# @private
|
249
|
+
def connect server_id
|
250
|
+
mailbox = @mailboxes[server_id]
|
251
|
+
mailbox.connect { |err|
|
252
|
+
if err
|
253
|
+
@mailboxes.delete server_id if @mailboxes[server_id]
|
254
|
+
return
|
255
|
+
end
|
256
|
+
|
257
|
+
mailbox.on(:close) { |id|
|
258
|
+
@mailboxes.delete id if @mailboxes[id]
|
259
|
+
emit :close, id
|
260
|
+
}
|
261
|
+
|
262
|
+
@connecting.delete server_id
|
263
|
+
flush_pending server_id
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
267
|
+
# Do before or after filter
|
268
|
+
#
|
269
|
+
# @param [Object] err
|
270
|
+
# @param [String] server_id
|
271
|
+
# @param [Hash] msg
|
272
|
+
# @param [Hash] opts
|
273
|
+
# @param [Array] filters
|
274
|
+
# @param [Integer] index
|
275
|
+
# @param [String] operate
|
276
|
+
# @param [#call] block
|
277
|
+
#
|
278
|
+
# @private
|
279
|
+
def do_filter err, server_id, msg, opts, filters, index, operate, block
|
280
|
+
if index >= filters.length || err
|
281
|
+
block.call err, server_id, msg, opts
|
282
|
+
return
|
283
|
+
end
|
284
|
+
|
285
|
+
filter = filters[index]
|
286
|
+
if filter.respond_to? :call
|
287
|
+
filter.call(server_id, msg, opts) { |target, message, options|
|
288
|
+
index += 1
|
289
|
+
if target.is_a? Exception
|
290
|
+
do_filter target, server_id, msg, opts, filters, index, operate, block
|
291
|
+
else
|
292
|
+
do_filter nil, target || server_id, message || msg, options || opts, filters, index, operate, block
|
293
|
+
end
|
294
|
+
}
|
295
|
+
return
|
296
|
+
end
|
297
|
+
|
298
|
+
index += 1
|
299
|
+
do_filter err, server_id, msg, opts, filters, index, operate, block
|
300
|
+
end
|
301
|
+
|
302
|
+
# Lazy connect remote server
|
303
|
+
#
|
304
|
+
# @param [String] server_id
|
305
|
+
# @param [Class] mailbox_class
|
306
|
+
#
|
307
|
+
# @private
|
308
|
+
def lazy_connect server_id, mailbox_class
|
309
|
+
unless server = @servers[server_id]
|
310
|
+
return false
|
311
|
+
end
|
312
|
+
unless @onlines[server_id] == true
|
313
|
+
return false
|
314
|
+
end
|
315
|
+
|
316
|
+
mailbox = mailbox_class.new server, @args
|
317
|
+
@connecting[server_id] = true
|
318
|
+
@mailboxes[server_id] = mailbox
|
319
|
+
connect server_id
|
320
|
+
|
321
|
+
true
|
322
|
+
end
|
323
|
+
|
324
|
+
# Add request to pending queue
|
325
|
+
#
|
326
|
+
# @param [String] server_id
|
327
|
+
# @param [Array] args
|
328
|
+
#
|
329
|
+
# @private
|
330
|
+
def add_to_pending server_id, args
|
331
|
+
pending = @pendings[server_id] ||= []
|
332
|
+
if pending.length > @pending_size
|
333
|
+
return
|
334
|
+
end
|
335
|
+
pending << args
|
336
|
+
end
|
337
|
+
|
338
|
+
# Flush pending queue
|
339
|
+
#
|
340
|
+
# @param [String] server_id
|
341
|
+
#
|
342
|
+
# @private
|
343
|
+
def flush_pending server_id
|
344
|
+
pending = @pendings[server_id]
|
345
|
+
mailbox = @mailboxes[server_id]
|
346
|
+
return unless pending && pending.length > 0
|
347
|
+
|
348
|
+
unless mailbox
|
349
|
+
end
|
350
|
+
|
351
|
+
pending.each { |args| dispatch *args }
|
352
|
+
|
353
|
+
@pendings.delete server_id
|
354
|
+
end
|
355
|
+
|
356
|
+
# Error handler
|
357
|
+
#
|
358
|
+
# @private
|
359
|
+
def error_handler
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|