pomelo-citrus-rpc 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +20 -0
  3. data/Rakefile +0 -0
  4. data/citrus-rpc.gemspec +32 -0
  5. data/example/client.rb +43 -0
  6. data/example/remote/test/service.rb +9 -0
  7. data/example/server.rb +20 -0
  8. data/lib/citrus-rpc.rb +16 -0
  9. data/lib/citrus-rpc/rpc-client/client.rb +328 -0
  10. data/lib/citrus-rpc/rpc-client/mailboxes/ws_mailbox.rb +164 -0
  11. data/lib/citrus-rpc/rpc-client/mailstation.rb +363 -0
  12. data/lib/citrus-rpc/rpc-client/proxy.rb +37 -0
  13. data/lib/citrus-rpc/rpc-client/router.rb +63 -0
  14. data/lib/citrus-rpc/rpc-server/acceptors/ws_acceptor.rb +143 -0
  15. data/lib/citrus-rpc/rpc-server/dispatcher.rb +36 -0
  16. data/lib/citrus-rpc/rpc-server/gateway.rb +58 -0
  17. data/lib/citrus-rpc/rpc-server/server.rb +92 -0
  18. data/lib/citrus-rpc/util/constants.rb +20 -0
  19. data/lib/citrus-rpc/util/utils.rb +42 -0
  20. data/lib/citrus-rpc/version.rb +7 -0
  21. data/spec/mock-remote/area/add_one_remote.rb +13 -0
  22. data/spec/mock-remote/area/add_three_remote.rb +9 -0
  23. data/spec/mock-remote/area/who_am_i_remote.rb +9 -0
  24. data/spec/mock-remote/connector/who_am_i_remote.rb +9 -0
  25. data/spec/rpc-client/client_spec.rb +166 -0
  26. data/spec/rpc-client/mailstaion_spec.rb +235 -0
  27. data/spec/rpc-client/proxy_spec.rb +8 -0
  28. data/spec/rpc-client/router_spec.rb +8 -0
  29. data/spec/rpc-client/ws_mailbox_spec.rb +144 -0
  30. data/spec/rpc-server/client/mock-tcp-client.rb +6 -0
  31. data/spec/rpc-server/client/mock-ws-client.rb +48 -0
  32. data/spec/rpc-server/dispatcher_spec.rb +88 -0
  33. data/spec/rpc-server/gateway_spec.rb +206 -0
  34. data/spec/rpc-server/server_spec.rb +79 -0
  35. data/spec/rpc-server/ws_acceptor_spec.rb +138 -0
  36. data/spec/spec_helper.rb +25 -0
  37. 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