pomelo-citrus 0.0.1

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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +20 -0
  3. data/Rakefile +0 -0
  4. data/citrus.gemspec +35 -0
  5. data/lib/citrus.rb +18 -0
  6. data/lib/citrus/application.rb +237 -0
  7. data/lib/citrus/citrus.rb +27 -0
  8. data/lib/citrus/common/remote/backend/msg_remote.rb +57 -0
  9. data/lib/citrus/common/remote/frontend/channel_remote.rb +73 -0
  10. data/lib/citrus/common/remote/frontend/session_remote.rb +108 -0
  11. data/lib/citrus/common/service/backend_session_service.rb +265 -0
  12. data/lib/citrus/common/service/channel_service.rb +485 -0
  13. data/lib/citrus/common/service/connection_service.rb +71 -0
  14. data/lib/citrus/common/service/filter_service.rb +92 -0
  15. data/lib/citrus/common/service/handler_service.rb +63 -0
  16. data/lib/citrus/common/service/session_service.rb +446 -0
  17. data/lib/citrus/components/backend_session.rb +32 -0
  18. data/lib/citrus/components/channel.rb +33 -0
  19. data/lib/citrus/components/component.rb +19 -0
  20. data/lib/citrus/components/connection.rb +48 -0
  21. data/lib/citrus/components/connector.rb +265 -0
  22. data/lib/citrus/components/master.rb +40 -0
  23. data/lib/citrus/components/monitor.rb +48 -0
  24. data/lib/citrus/components/proxy.rb +195 -0
  25. data/lib/citrus/components/push_scheduler.rb +74 -0
  26. data/lib/citrus/components/remote.rb +71 -0
  27. data/lib/citrus/components/server.rb +61 -0
  28. data/lib/citrus/components/session.rb +41 -0
  29. data/lib/citrus/connectors/commands/handshake.rb +22 -0
  30. data/lib/citrus/connectors/commands/heartbeat.rb +22 -0
  31. data/lib/citrus/connectors/commands/kick.rb +22 -0
  32. data/lib/citrus/connectors/common/coder.rb +21 -0
  33. data/lib/citrus/connectors/common/handler.rb +21 -0
  34. data/lib/citrus/connectors/ws_connector.rb +110 -0
  35. data/lib/citrus/connectors/ws_socket.rb +75 -0
  36. data/lib/citrus/filters/handler/handler_filter.rb +19 -0
  37. data/lib/citrus/filters/handler/too_busy.rb +16 -0
  38. data/lib/citrus/filters/rpc/rpc_filter.rb +19 -0
  39. data/lib/citrus/filters/rpc/too_busy.rb +16 -0
  40. data/lib/citrus/master/master.rb +60 -0
  41. data/lib/citrus/master/starter.rb +73 -0
  42. data/lib/citrus/master/watchdog.rb +83 -0
  43. data/lib/citrus/modules/console.rb +45 -0
  44. data/lib/citrus/modules/console_module.rb +35 -0
  45. data/lib/citrus/modules/master_watcher.rb +88 -0
  46. data/lib/citrus/modules/monitor_watcher.rb +86 -0
  47. data/lib/citrus/monitor/monitor.rb +61 -0
  48. data/lib/citrus/push_schedulers/buffer.rb +16 -0
  49. data/lib/citrus/push_schedulers/direct.rb +76 -0
  50. data/lib/citrus/server/server.rb +327 -0
  51. data/lib/citrus/util/app_util.rb +203 -0
  52. data/lib/citrus/util/constants.rb +19 -0
  53. data/lib/citrus/util/countdown_latch.rb +42 -0
  54. data/lib/citrus/util/events.rb +14 -0
  55. data/lib/citrus/util/module_util.rb +68 -0
  56. data/lib/citrus/util/path_util.rb +50 -0
  57. data/lib/citrus/util/utils.rb +49 -0
  58. data/lib/citrus/version.rb +7 -0
  59. metadata +241 -0
@@ -0,0 +1,71 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 25 July 2014
4
+
5
+ module Citrus
6
+ # Common
7
+ #
8
+ #
9
+ module Common
10
+ # Service
11
+ #
12
+ #
13
+ module Service
14
+ # ConnectionService
15
+ #
16
+ #
17
+ class ConnectionService
18
+ # Initialize the service
19
+ #
20
+ # @param [Object] app
21
+ def initialize app
22
+ @server_id = app.server_id
23
+ @conn_count = 0
24
+ @logined_count = 0
25
+ @logined = {}
26
+ end
27
+
28
+ # Add logined user
29
+ #
30
+ # @param [String] uid
31
+ # @param [Hash] info
32
+ def add_logined_user uid, info={}
33
+ @logined_count += 1 unless @logined[uid]
34
+ info[:uid] = uid
35
+ @logined[uid] = info
36
+ end
37
+
38
+ # Increase connection count
39
+ def increase_conn_count
40
+ @conn_count += 1
41
+ end
42
+
43
+ # Remove logined user
44
+ #
45
+ # @param [String] uid
46
+ def remove_logined_user uid
47
+ @logined_count -= 1 if @logined[uid]
48
+ @logined.delete uid
49
+ end
50
+
51
+ # Decrease connection count
52
+ #
53
+ # @param [String] uid
54
+ def decrease_conn_count uid
55
+ @conn_count -= 1 if @conn_count > 0
56
+ remove_logined_user uid unless uid.empty?
57
+ end
58
+
59
+ # Get statistics info
60
+ def get_statistics_info
61
+ {
62
+ :server_id => @server_id,
63
+ :conn_count => @conn_count,
64
+ :logined_count => @logined_count,
65
+ :logined_list => @logined.values
66
+ }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,92 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 29 July 2014
4
+
5
+ module Citrus
6
+ # Common
7
+ #
8
+ #
9
+ module Common
10
+ # Service
11
+ #
12
+ #
13
+ module Service
14
+ # FilterService
15
+ #
16
+ #
17
+ class FilterService
18
+ # Initialize the service
19
+ def initialize
20
+ @befores = []
21
+ @afters = []
22
+ end
23
+
24
+ # Add before filter into the filter chain
25
+ #
26
+ # @param [#call] filter
27
+ def before filter
28
+ @befores << filter
29
+ end
30
+
31
+ # Add after filter into the filter chain
32
+ #
33
+ # @param [#call] filter
34
+ def after filter
35
+ @afters.unshift filter
36
+ end
37
+
38
+ # Do the before filter chain
39
+ #
40
+ # @param [Hash] msg
41
+ # @param [Object] session
42
+ def before_filter msg, session, &block
43
+ index = 0
44
+
45
+ next_p = Proc.new { |err, resp, args|
46
+ if err || index >= @befores.length
47
+ block_given? and yield err, resp, args
48
+ return
49
+ end
50
+
51
+ handler = @befores[index]
52
+ index += 1
53
+
54
+ if handler.respond_to? :call
55
+ handler.call msg, session, &next_p
56
+ else
57
+ next_p.call Exception.new 'invalid before filter'
58
+ end
59
+ }
60
+ next_p.call
61
+ end
62
+
63
+ # Do the after filter chain
64
+ #
65
+ # @param [Object] err
66
+ # @param [Hash] msg
67
+ # @param [Object] session
68
+ # @param [Hash] resp
69
+ def after_filter err, msg, session, resp, &block
70
+ index = 0
71
+
72
+ next_p = Proc.new { |err|
73
+ if index >= @afters.length
74
+ block_given? and yield err
75
+ return
76
+ end
77
+
78
+ handler = @afters[index]
79
+ index += 1
80
+
81
+ if handler.respond_to? :call
82
+ handler.call err, msg, session, resp, &next_p
83
+ else
84
+ next_p.call Exception.new 'invalid after filter'
85
+ end
86
+ }
87
+ next_p.call err
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,63 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 29 July 2014
4
+
5
+ module Citrus
6
+ # Common
7
+ #
8
+ #
9
+ module Common
10
+ # Service
11
+ #
12
+ #
13
+ module Service
14
+ # HandlerService
15
+ #
16
+ #
17
+ class HandlerService
18
+ # Initialize the service
19
+ #
20
+ # @param [Object] app
21
+ # @param [Hash] handlers
22
+ def initialize app, handlers={}
23
+ @app = app
24
+ @handlers = handlers
25
+ end
26
+
27
+ # Handle message from the client
28
+ #
29
+ # @param [Hash] route_record
30
+ # @param [Hash] msg
31
+ # @param [Object] session
32
+ def handle route_record, msg, session, &block
33
+ handler = get_handler route_record
34
+ unless handler
35
+ block_given? and yield Exception.new 'failed to find the handler'
36
+ return
37
+ end
38
+ handler.send(route_record['method'], msg, session) { |err, resp, args|
39
+ block_given? and yield err, resp, args
40
+ }
41
+ end
42
+
43
+ private
44
+
45
+ # Get handler by route record
46
+ #
47
+ # @param [Hash] route_record
48
+ #
49
+ # @private
50
+ def get_handler route_record
51
+ handler = @handlers[route_record['handler']]
52
+ unless handler
53
+ return nil
54
+ end
55
+ unless handler.respond_to? route_record['method']
56
+ return nil
57
+ end
58
+ handler
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,446 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 19 July 2014
4
+
5
+ module Citrus
6
+ # Common
7
+ #
8
+ #
9
+ module Common
10
+ # Service
11
+ #
12
+ #
13
+ module Service
14
+ # SessionService
15
+ #
16
+ #
17
+ class SessionService
18
+ #
19
+ #
20
+ #
21
+ FRONTEND_SESSION_FIELDS = ['id', 'frontend_id', 'uid', 'session_service']
22
+ EXPORTED_SESSION_FIELDS = ['id', 'frontend_id', 'uid', 'settings']
23
+
24
+ attr_reader :sessions
25
+
26
+ # Initialize the service
27
+ #
28
+ # @param [Hash] args
29
+ def initialize args={}
30
+ @single_session = args[:single_session] || false
31
+ @sessions = {}
32
+ @uid_map = {}
33
+ end
34
+
35
+ # Create a new session
36
+ #
37
+ # @param [Integer] sid
38
+ # @param [String] frontend_id
39
+ # @param [Object] socket
40
+ def create sid, frontend_id, socket
41
+ session = Session.new sid, frontend_id, socket, self
42
+ @sessions[session.id] = session
43
+ session
44
+ end
45
+
46
+ # Bind the session with a user id
47
+ #
48
+ # @param [Integer] sid
49
+ # @param [String] user_id
50
+ def bind sid, user_id, &block
51
+ session = @sessions[sid]
52
+ unless session
53
+ EM.next_tick {
54
+ block_given? and yield Exception.new 'session does not exist, sid: ' + sid
55
+ }
56
+ return
57
+ end
58
+
59
+ if session.uid
60
+ if session.uid == uid
61
+ # already bound with the same uid
62
+ block_given? and yield
63
+ return
64
+ end
65
+
66
+ # already bound with another uid
67
+ EM.next_tick {
68
+ block_given? and yield Exception.new 'session has already bound with ' + session.uid
69
+ }
70
+ return
71
+ end
72
+
73
+ sessions = @uid_map[uid]
74
+
75
+ if sessions && @single_session
76
+ EM.next_tick {
77
+ block_given? and yield Exception.new 'single_session is enabled and session has already bound with uid: ' + uid
78
+ }
79
+ return
80
+ end
81
+
82
+ sessions = @uid_map[uid] = [] unless sessions
83
+
84
+ sessions.each { |s|
85
+ # session has binded with the uid
86
+ if s.id == session.id
87
+ EM.next_tick { block_given? and yield }
88
+ return
89
+ end
90
+ }
91
+ sessions << session
92
+
93
+ session.bind uid
94
+
95
+ EM.next_tick { yield } if block_given?
96
+ end
97
+
98
+ # Unbind a session with the user id
99
+ #
100
+ # @param [Integer] sid
101
+ # @param [String] uid
102
+ def unbind sid, uid, &block
103
+ session = @sessions[sid]
104
+ unless session
105
+ EM.next_tick {
106
+ block_given? and yield Exception.new 'session does not exist, sid: ' + sid
107
+ }
108
+ return
109
+ end
110
+
111
+ unless session.uid && session.uid == uid
112
+ EM.next_tick {
113
+ block_given? and yield Exception.new 'session has not bind with ' + uid
114
+ }
115
+ return
116
+ end
117
+
118
+ sessions = @uid_map[uid]
119
+ if sessions
120
+ sessions.each { |s|
121
+ if s.id == sid
122
+ sessions.delete s
123
+ break
124
+ end
125
+ }
126
+
127
+ if sessions.length == 0
128
+ @uid_map.delete uid
129
+ end
130
+ end
131
+ session.unbind uid
132
+
133
+ EM.next_tick { yield } if block_given?
134
+ end
135
+
136
+ # Get session by id
137
+ #
138
+ # @param [Integer] sid
139
+ def get sid
140
+ @sessions[sid]
141
+ end
142
+
143
+ # Get sessions by user id
144
+ #
145
+ # @param [String] uid
146
+ def get_by_uid uid
147
+ @uid_map[uid]
148
+ end
149
+
150
+ # Remove session by session id
151
+ #
152
+ # @param [Integer] sid
153
+ def remove sid
154
+ if session = @sessions[sid]
155
+ uid = session.uid
156
+ @sessions.delete session.id
157
+
158
+ sessions = @uid_map[uid]
159
+ return unless sessions
160
+
161
+ sessions.each { |s|
162
+ if s.id == sid
163
+ sessions.delete s
164
+ @uid_map.delete uid if sessions.length == 0
165
+ end
166
+ }
167
+ end
168
+ end
169
+
170
+ # Import the key/value into session
171
+ #
172
+ # @param [Integer] sid
173
+ # @param [String] key
174
+ # @param [Hash] value
175
+ def import sid, key, value, &block
176
+ session = @sessions[sid]
177
+ unless session
178
+ block_given? and yield 'session does not exist, sid: ' + sid
179
+ return
180
+ end
181
+ session.set key, value
182
+ block_given? and yield
183
+ end
184
+
185
+ # Import new value for the existed session
186
+ #
187
+ # @param [Integer] sid
188
+ # @param [Hash] settings
189
+ def import_all sid, settings, &block
190
+ session = @sessions[sid]
191
+ unless session
192
+ block_given? and yield 'session does not exist, sid: ' + sid
193
+ return
194
+ end
195
+
196
+ settings.each_pair { |key, value| session.set key, value }
197
+ block_given? and yield
198
+ end
199
+
200
+ # Kick all the sessions offline under the user id
201
+ #
202
+ # @param [String] uid
203
+ # @param [String] reason
204
+ def kick uid, reason='', &block
205
+ if sessions = get_by_uid(uid)
206
+ # notify client
207
+ sids = []
208
+ sessions.each { |session| sids << session.id }
209
+ sids.each { |sid| @sessions[sid].closed reason }
210
+ end
211
+ EM.next_tick { block_given? and yield }
212
+ end
213
+
214
+ # Kick a user offline by session id
215
+ #
216
+ # @param [Integer] sid
217
+ def kick_by_session_id sid, &block
218
+ session = get sid
219
+ session.closed 'kick' if session
220
+ EM.next_tick { block_given? and yield }
221
+ end
222
+
223
+ # Get client remote address by session id
224
+ #
225
+ # @param [Integer] sid
226
+ def get_client_address_by_session_id sid
227
+ session = get sid
228
+ return session.socket.remote_addres if session
229
+ return nil
230
+ end
231
+
232
+ # Send message to the client by session id
233
+ #
234
+ # @param [Integer] sid
235
+ # @param [Hash] msg
236
+ def send_message sid, msg
237
+ session = @sessions[sid]
238
+
239
+ unless session
240
+ return false
241
+ end
242
+
243
+ send session, msg
244
+ end
245
+
246
+ # Send message to the client by user id
247
+ #
248
+ # @param [String] uid
249
+ # @param [Hash] msg
250
+ def send_message_by_uid uid, msg
251
+ sessions = @uid_map[uid]
252
+
253
+ unless sessions
254
+ return false
255
+ end
256
+
257
+ sessions.each { |session|
258
+ send session, msg
259
+ }
260
+ end
261
+
262
+ private
263
+
264
+ # Send message to the client that associated with the session
265
+ #
266
+ # @param [Object] session
267
+ # @param [Hash] msg
268
+ #
269
+ # @private
270
+ def send session, msg
271
+ session.send msg; true
272
+ end
273
+
274
+ # Session
275
+ #
276
+ #
277
+ class Session
278
+ include Utils::EventEmitter
279
+
280
+ attr_reader :id, :frontend_id, :uid, :settings, :session_service
281
+
282
+ # Create a new session
283
+ #
284
+ # @param [Integer] sid
285
+ # @param [String] frontend_id
286
+ # @param [Object] socket
287
+ # @param [Object] service
288
+ def initialize sid, frontend_id, socket, service
289
+ @id = sid
290
+ @frontend_id = frontend_id
291
+ @uid = nil
292
+ @settings = {}
293
+
294
+ @socket = socket
295
+ @session_service = service
296
+ @state = :state_inited
297
+ end
298
+
299
+ # Export current session as frontend session
300
+ def to_frontend_session
301
+ FrontendSession.new self
302
+ end
303
+
304
+ # Bind the session with the uid
305
+ #
306
+ # @param [String] uid
307
+ def bind uid
308
+ @uid = uid
309
+ emit :bind, uid
310
+ end
311
+
312
+ # Unbind the session with the uid
313
+ #
314
+ # @param [String] uid
315
+ def unbind uid
316
+ @uid = nil
317
+ emit :unbind, uid
318
+ end
319
+
320
+ # Set value for the session
321
+ #
322
+ # @param [String] key
323
+ # @param [Hash] value
324
+ def set key, value
325
+ @settings[key] = value
326
+ end
327
+
328
+ # Get value from the session
329
+ #
330
+ # @param [String] key
331
+ def get key
332
+ @settings[key]
333
+ end
334
+
335
+ # Send message to the session
336
+ #
337
+ # @param [Hash] msg
338
+ def send msg
339
+ @socket.send msg
340
+ end
341
+
342
+ # Send message to the session in batch
343
+ #
344
+ # @param [Array] msgs
345
+ def send_batch msgs
346
+ @socket.send_batch msgs
347
+ end
348
+
349
+ # Closed callback for the session which would disconnect client in next tick
350
+ #
351
+ # @param [String] reason
352
+ def closed reason=''
353
+ return if @state == :state_closed
354
+
355
+ @state = :state_closed
356
+ @service.remove @id
357
+
358
+ emit :closed, to_frontend_session, reason
359
+ @socket.emit :closing, reason
360
+
361
+ EM.next_tick { @socket.disconnect }
362
+ end
363
+ end
364
+
365
+ # FrontendSession
366
+ #
367
+ #
368
+ class FrontendSession
369
+ include Utils::EventEmitter
370
+
371
+ # Create a new frontend session
372
+ #
373
+ # @param [Object] session
374
+ def initialize session
375
+ FRONTEND_SESSION_FIELDS.each { |field|
376
+ instance_eval %Q{ @#{field} = session.#{field} }
377
+ }
378
+ # deep copy for settings
379
+ @settings = session.settings.dup
380
+ @session = session
381
+ end
382
+
383
+ # Bind the frontend session with the uid
384
+ #
385
+ # @param [String] uid
386
+ def bind uid, &block
387
+ @session_service.bind(@id, uid) { |err|
388
+ unless err
389
+ @uid = uid
390
+ end
391
+ block_given? and yield err
392
+ }
393
+ end
394
+
395
+ # Unbind the session with the uid
396
+ #
397
+ # @param [String] uid
398
+ def unbind uid, &block
399
+ @session_service.unbind(@id, uid) { |err|
400
+ unless err
401
+ @uid = nil
402
+ end
403
+ block_given? and yield err
404
+ }
405
+ end
406
+
407
+ # Set value for the frontend session
408
+ #
409
+ # @param [String] key
410
+ # @param [Hash] value
411
+ def set key, value
412
+ @settings[key] = value
413
+ end
414
+
415
+ # Get value from the frontend session
416
+ #
417
+ # @param [String] key
418
+ def get key
419
+ @settings[key]
420
+ end
421
+
422
+ # Push value to the internal session
423
+ #
424
+ # @param [String] key
425
+ def push key, &block
426
+ @session_service.import @id, key, get(key), &block
427
+ end
428
+
429
+ # Push all the key/value pairs to the internal session
430
+ def push_all &block
431
+ @session_service.import_all @id, @settings, &block
432
+ end
433
+
434
+ # Export the key/values for serialization
435
+ def export
436
+ res = {}
437
+ EXPORTED_SESSION_FIELDS.each { |field|
438
+ instance_eval %Q{ res['#{field}'] = @#{field} }
439
+ }
440
+ res
441
+ end
442
+ end
443
+ end
444
+ end
445
+ end
446
+ end