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,37 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 4 July 2014
4
+
5
+ module CitrusRpc
6
+ # RpcClient
7
+ #
8
+ #
9
+ module RpcClient
10
+ # Proxy
11
+ #
12
+ #
13
+ module Proxy
14
+ private
15
+
16
+ # Create proxy
17
+ #
18
+ # @param [Hash] args Options
19
+ #
20
+ # @option args [Class] remote
21
+ # @option args [Hash] attach
22
+ # @option args [#call] proxy_cb
23
+ #
24
+ # @private
25
+ def create_proxy args={}
26
+ res = Object.new
27
+ methods = args[:remote].instance_methods
28
+ methods.each { |method|
29
+ res.define_singleton_method method, proc{ |*inner_args, &block|
30
+ args[:proxy_cb].call args[:service], method, args[:attach], false, *inner_args, &block
31
+ }
32
+ }
33
+ res
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,63 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 5 July 2014
4
+
5
+ module CitrusRpc
6
+ # RpcClient
7
+ #
8
+ #
9
+ module RpcClient
10
+ # Router
11
+ #
12
+ #
13
+ module Router
14
+ # Calculate route info and return an appropriate server id
15
+ #
16
+ # @param [Object] session
17
+ # @param [Hash] msg
18
+ # @param [Object] context
19
+ def df_route session, msg, context, &block
20
+ end
21
+
22
+ # Random algorithm for calculating server id
23
+ #
24
+ # @param [Object] client
25
+ # @param [String] server_type
26
+ # @param [Hash] msg
27
+ def rd_route client, server_type, msg, &block
28
+ end
29
+
30
+ # Round-Robin algorithm for calculating server id
31
+ #
32
+ # @param [Object] client
33
+ # @param [String] server_type
34
+ # @param [Hash] msg
35
+ def rr_route client, server_type, msg, &block
36
+ end
37
+
38
+ # Weight-Round-Robin algorithm for calculating server id
39
+ #
40
+ # @param [Object] client
41
+ # @param [String] server_type
42
+ # @param [Hash] msg
43
+ def wrr_route client, server_type, msg, &block
44
+ end
45
+
46
+ # Least-Active algorithm for calculating server id
47
+ #
48
+ # @param [Object] client
49
+ # @param [String] server_type
50
+ # @param [Hash] msg
51
+ def la_route client, server_type, msg, &block
52
+ end
53
+
54
+ # Consistent-Hash algorithm for calculating server id
55
+ #
56
+ # @param [Object] client
57
+ # @param [String] server_type
58
+ # @param [Hash] msg
59
+ def ch_route client, server_type, msg, &block
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,143 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 5 July 2014
4
+
5
+ require 'websocket-eventmachine-server'
6
+
7
+ module CitrusRpc
8
+ # RpcServer
9
+ #
10
+ #
11
+ module RpcServer
12
+ # WsAcceptor
13
+ #
14
+ #
15
+ class WsAcceptor
16
+ include Utils::EventEmitter
17
+
18
+ # Create a new websocket acceptor
19
+ #
20
+ # @param [Hash] args Options
21
+ #
22
+ # @option args [Boolean] buffer_msg
23
+ # @option args [Integer] interval
24
+ def initialize args={}, &block
25
+ @buffer_msg = args[:buffer_msg]
26
+ @interval = args[:interval]
27
+
28
+ @server = nil
29
+ @wss = {}
30
+
31
+ @msg_queues = {}
32
+ @callback = block
33
+
34
+ @listening = false
35
+ @closed = false
36
+ end
37
+
38
+ # Listen on port
39
+ #
40
+ # @param [Integer] port
41
+ def listen port
42
+ raise RuntimeError 'acceptor double listen' if @listening
43
+
44
+ begin
45
+ @server = WebSocket::EventMachine::Server.start(:host => '0.0.0.0', :port => port) { |ws|
46
+ ws.onopen {
47
+ @wss[ws.signature] = ws
48
+ peer_port, peer_host = Socket.unpack_sockaddr_in ws.get_peername
49
+ emit :connection, { :id => ws.signature, :ip => peer_host }
50
+ }
51
+
52
+ ws.onmessage { |msg, type|
53
+ begin
54
+ pkg = JSON.parse msg
55
+ if pkg.instance_of? Array
56
+ process_msgs ws, pkg
57
+ else
58
+ process_msg ws, pkg
59
+ end
60
+ rescue => err
61
+ end
62
+ }
63
+
64
+ ws.onclose {
65
+ @wss.delete ws.signature
66
+ @msg_queues.delete ws.signature
67
+ }
68
+ ws.onerror { |err| emit :error, err }
69
+ }
70
+ rescue => err
71
+ emit :error, err
72
+ end
73
+
74
+ on(:connection) { |obj| ip_filter obj }
75
+ @listening = true
76
+ end
77
+
78
+ # Close the acceptor
79
+ def close
80
+ return unless @listening && !@closed
81
+ EM.stop_server @server
82
+ @closed = true
83
+ emit :closed
84
+ end
85
+
86
+ private
87
+
88
+ # Clone error
89
+ #
90
+ # @private
91
+ def clone_error origin
92
+ { 'msg' => origin.message, 'stack' => nil }
93
+ end
94
+
95
+ # Process message
96
+ #
97
+ # @param [Object] ws
98
+ # @param [Hash] pkg
99
+ #
100
+ # @private
101
+ def process_msg ws, pkg
102
+ @callback.call pkg['msg'] { |*args|
103
+ args.each_with_index { |arg, index|
104
+ args[index] = clone_error arg if arg.is_a? Exception
105
+ }
106
+
107
+ resp = { 'id' => pkg['id'], 'resp' => args }
108
+ if @buffer_msg
109
+ enqueue ws, resp
110
+ else
111
+ ws.send resp.to_json
112
+ end
113
+ }
114
+ end
115
+
116
+ # Batch version for process_msg
117
+ #
118
+ # @param [Object] ws
119
+ # @param [Array] pkgs
120
+ #
121
+ # @private
122
+ def process_msgs ws, pkgs
123
+ pkgs.each { |pkg| process_msg ws, pkg }
124
+ end
125
+
126
+ # Enqueue the response
127
+ #
128
+ # @param [Object] ws
129
+ #
130
+ # @private
131
+ def enqueue ws, resp
132
+ end
133
+
134
+ # ip filter
135
+ #
136
+ # @param [Object] obj
137
+ #
138
+ # @private
139
+ def ip_filter obj
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,36 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 5 July 2014
4
+
5
+ module CitrusRpc
6
+ # RpcServer
7
+ #
8
+ #
9
+ module RpcServer
10
+ # Dispatcher
11
+ #
12
+ #
13
+ module Dispatcher
14
+ # Dispatch message to appropriate service object
15
+ #
16
+ # @param [Hash] msg
17
+ # @param [Hash] services
18
+ def dispatch msg, services, &block
19
+ unless namespace = services[msg['namespace']]
20
+ block.call Exception.new 'no such namespace: ' + msg['namespace']
21
+ return
22
+ end
23
+ unless service = namespace[msg['service']]
24
+ block.call Exception.new 'no such service: ' + msg['service']
25
+ return
26
+ end
27
+ unless service.respond_to? msg['method']
28
+ block.call Exception.new 'no such method: ' + msg['method']
29
+ return
30
+ end
31
+
32
+ service.send msg['method'], *msg['args'], &block
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,58 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 5 July 2014
4
+
5
+ require 'citrus-rpc/rpc-server/acceptors/ws_acceptor'
6
+ require 'citrus-rpc/rpc-server/dispatcher'
7
+
8
+ module CitrusRpc
9
+ # RpcServer
10
+ #
11
+ #
12
+ module RpcServer
13
+ # Gateway
14
+ #
15
+ #
16
+ class Gateway
17
+ include Dispatcher
18
+ include Utils::EventEmitter
19
+
20
+ # Create a gateway
21
+ #
22
+ # @param [Hash] args Options
23
+ #
24
+ # @option args [Integer] port
25
+ # @option args [Class] acceptor_class
26
+ # @option args [Hash] services
27
+ def initialize args={}
28
+ @port = args[:port] || 3050
29
+ @started = false
30
+ @stoped = false
31
+
32
+ @acceptor_class = args[:acceptor_class] || WsAcceptor
33
+ @services = args[:services]
34
+
35
+ @acceptor = @acceptor_class.new(args) { |msg, &block|
36
+ dispatch msg, @services, &block
37
+ }
38
+ end
39
+
40
+ # Start the gateway
41
+ def start
42
+ raise RuntimeError 'gateway already started' if @started
43
+ @started = true
44
+
45
+ @acceptor.on(:error) { |*args| emit :error, *args }
46
+ @acceptor.on(:closed) { |*args| emit :closed, *args }
47
+ @acceptor.listen @port
48
+ end
49
+
50
+ # Stop the gateway
51
+ def stop
52
+ return unless @started && !@stoped
53
+ @stoped = true
54
+ @acceptor.close
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,92 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 4 July 2014
4
+
5
+ require 'citrus-rpc/rpc-server/gateway'
6
+
7
+ module CitrusRpc
8
+ # RpcServer
9
+ #
10
+ #
11
+ module RpcServer
12
+ # Server
13
+ #
14
+ # @example
15
+ #
16
+ # Specifiy remote service interface paths
17
+ #
18
+ # dirname = File.expand_path File.dirname(__FILE__)
19
+ # records = [
20
+ # { :namespace => 'user', :path => dirname + '/remote/test' }
21
+ # ]
22
+ #
23
+ # Create a new rpc server and start it
24
+ #
25
+ # Server.new(:records => records, :port => 3333).start
26
+ #
27
+ class Server
28
+ include CitrusLoader
29
+ include Utils::EventEmitter
30
+
31
+ # Create a new rpc server
32
+ #
33
+ # @param [Hash] args Options
34
+ #
35
+ # @option args [Integer] port
36
+ # @option args [Array] records
37
+ def initialize args={}
38
+ raise ArgumentError, 'server port empty' unless args[:port]
39
+ raise ArgumentError, 'server port must be bigger than zero' unless args[:port] > 0
40
+ raise ArgumentError, 'records empty' unless args[:records]
41
+ raise ArgumentError, 'records must be an array' unless args[:records].respond_to? :to_a
42
+
43
+ @services = {}
44
+
45
+ create_namespace args[:records]
46
+ load_remote_services args[:records], args[:context]
47
+
48
+ args[:services] = @services
49
+
50
+ @gateway = Gateway.new args
51
+ @gateway.on(:error) { |*args| emit :error, *args }
52
+ @gateway.on(:closed) { |*args| emit :closed, *args }
53
+ end
54
+
55
+ # Start the rpc server
56
+ def start
57
+ @gateway.start
58
+ end
59
+
60
+ # Stop the rpc server
61
+ def stop
62
+ @gateway.stop
63
+ end
64
+
65
+ private
66
+
67
+ # Create remote services namespace
68
+ #
69
+ # @param [Array] records
70
+ #
71
+ # @private
72
+ def create_namespace records
73
+ records.each { |record| @services[record[:namespace]] ||= {} }
74
+ end
75
+
76
+ # Load remote services
77
+ #
78
+ # @param [Array] records
79
+ # @param [Object] context
80
+ #
81
+ # @private
82
+ def load_remote_services records, context
83
+ records.each { |record|
84
+ remotes = load_app_remote record[:path]
85
+ remotes.each { |service, remote|
86
+ @services[record[:namespace]][service] = remote.new context
87
+ }
88
+ }
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,20 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 5 July 2014
4
+
5
+ module CitrusRpc
6
+ # Constants
7
+ #
8
+ #
9
+ module Constants
10
+ # DefaultParams
11
+ #
12
+ #
13
+ module DefaultParams
14
+ GraceTimeout = 3
15
+ Interval = 5
16
+ PendingSize = 1000
17
+ Timeout = 30
18
+ end
19
+ end
20
+ end