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