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