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.
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