nats 0.4.28 → 0.5.0.beta.12
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 +15 -0
- data/HISTORY.md +16 -0
- data/README.md +8 -5
- data/bin/nats-queue +14 -5
- data/bin/nats-request +12 -3
- data/bin/nats-sub +13 -5
- data/lib/nats/client.rb +189 -35
- data/lib/nats/server.rb +27 -4
- data/lib/nats/server/cluster.rb +102 -0
- data/lib/nats/server/connection.rb +47 -17
- data/lib/nats/server/connz.rb +0 -2
- data/lib/nats/server/const.rb +14 -2
- data/lib/nats/server/options.rb +19 -2
- data/lib/nats/server/route.rb +279 -0
- data/lib/nats/server/server.rb +35 -10
- data/lib/nats/server/varz.rb +1 -0
- data/nats.gemspec +6 -4
- metadata +38 -26
data/lib/nats/server.rb
CHANGED
@@ -13,6 +13,8 @@ require "#{ep}/server/server"
|
|
13
13
|
require "#{ep}/server/sublist"
|
14
14
|
require "#{ep}/server/connection"
|
15
15
|
require "#{ep}/server/options"
|
16
|
+
require "#{ep}/server/cluster"
|
17
|
+
require "#{ep}/server/route"
|
16
18
|
require "#{ep}/server/const"
|
17
19
|
require "#{ep}/server/util"
|
18
20
|
require "#{ep}/server/varz"
|
@@ -22,12 +24,13 @@ require "#{ep}/server/connz"
|
|
22
24
|
NATSD::Server.setup(ARGV.dup)
|
23
25
|
|
24
26
|
# Event Loop
|
25
|
-
EM.run
|
27
|
+
EM.run do
|
28
|
+
|
26
29
|
log "Starting #{NATSD::APP_NAME} version #{NATSD::VERSION} on port #{NATSD::Server.port}"
|
27
|
-
log "TLS/SSL Support Enabled" if NATSD::Server.options[:ssl]
|
30
|
+
log "TLS/SSL Support Enabled" if NATSD::Server.options[:ssl]
|
28
31
|
begin
|
29
32
|
EM.set_descriptor_table_size(32768) # Requires Root privileges
|
30
|
-
|
33
|
+
EM.start_server(NATSD::Server.host, NATSD::Server.port, NATSD::Connection)
|
31
34
|
rescue => e
|
32
35
|
log "Could not start server on port #{NATSD::Server.port}"
|
33
36
|
log_error
|
@@ -44,4 +47,24 @@ EM.run {
|
|
44
47
|
exit(1)
|
45
48
|
end
|
46
49
|
end
|
47
|
-
|
50
|
+
|
51
|
+
###################
|
52
|
+
# CLUSTER SETUP
|
53
|
+
###################
|
54
|
+
|
55
|
+
# Check to see if we need to fire up a routing listen port
|
56
|
+
if NATSD::Server.options[:cluster_port]
|
57
|
+
begin
|
58
|
+
log "Starting routing on port #{NATSD::Server.options[:cluster_port]}"
|
59
|
+
EM.start_server(NATSD::Server.host, NATSD::Server.options[:cluster_port], NATSD::Route)
|
60
|
+
rescue => e
|
61
|
+
log "Could not start routing server on port #{NATSD::Server.options[:cluster_port]}"
|
62
|
+
log_error
|
63
|
+
exit(1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# If we have active connections, solicit them now..
|
68
|
+
NATSD::Server.solicit_routes if NATSD::Server.options[:cluster_routes]
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module NATSD #:nodoc: all
|
4
|
+
|
5
|
+
class Server
|
6
|
+
class << self
|
7
|
+
attr_reader :opt_routes, :route_auth_required, :route_ssl_required, :reconnect_interval
|
8
|
+
attr_accessor :num_routes
|
9
|
+
|
10
|
+
alias route_auth_required? :route_auth_required
|
11
|
+
alias route_ssl_required? :route_ssl_required
|
12
|
+
|
13
|
+
def connected_routes
|
14
|
+
@routes ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_route(route)
|
18
|
+
connected_routes << route unless route.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove_route(route)
|
22
|
+
connected_routes.delete(route) unless route.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def route_info_string
|
26
|
+
@route_info = {
|
27
|
+
:server_id => Server.id,
|
28
|
+
:host => @options[:cluster_net] || host,
|
29
|
+
:port => @options[:cluster_port],
|
30
|
+
:version => VERSION,
|
31
|
+
:auth_required => route_auth_required?,
|
32
|
+
:ssl_required => false, # FIXME!
|
33
|
+
:max_payload => @max_payload
|
34
|
+
}
|
35
|
+
@route_info.to_json
|
36
|
+
end
|
37
|
+
|
38
|
+
def route_key(route_url)
|
39
|
+
r = URI.parse(route_url)
|
40
|
+
"#{r.host}:#{r.port}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def route_auth_ok?(user, pass)
|
44
|
+
user == @options[:cluster_user] && pass == @options[:cluster_pass]
|
45
|
+
end
|
46
|
+
|
47
|
+
def solicit_routes #:nodoc:
|
48
|
+
@opt_routes = []
|
49
|
+
NATSD::Server.options[:cluster_routes].each do |r_url|
|
50
|
+
opt_routes << { :route => r_url, :uri => URI.parse(r_url), :key => route_key(r_url) }
|
51
|
+
end
|
52
|
+
try_to_connect_routes
|
53
|
+
end
|
54
|
+
|
55
|
+
def try_to_connect_routes #:nodoc:
|
56
|
+
opt_routes.each do |route|
|
57
|
+
# FIXME, Strip auth
|
58
|
+
debug "Trying to connect to route: #{route[:route]}"
|
59
|
+
EM.connect(route[:uri].host, route[:uri].port, NATSD::Route, route)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def broadcast_proto_to_routes(proto)
|
64
|
+
connected_routes.each { |r| r.queue_data(proto) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def rsid_qsub(rsid)
|
68
|
+
cid, sid = parse_rsid(rsid)
|
69
|
+
conn = Server.connections[cid]
|
70
|
+
sub = conn.subscriptions[sid]
|
71
|
+
sub if sub.qgroup
|
72
|
+
rescue
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_rsid(rsid)
|
77
|
+
m = RSID.match(rsid)
|
78
|
+
return [m[1].to_i, m[2]] if m
|
79
|
+
end
|
80
|
+
|
81
|
+
def routed_sid(sub)
|
82
|
+
"RSID:#{sub.conn.cid}:#{sub.sid}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def route_sub_proto(sub)
|
86
|
+
return "SUB #{sub.subject} #{routed_sid(sub)}#{CR_LF}" if sub.qgroup.nil?
|
87
|
+
return "SUB #{sub.subject} #{sub.qgroup} #{routed_sid(sub)}#{CR_LF}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def broadcast_sub_to_routes(sub)
|
91
|
+
broadcast_proto_to_routes(route_sub_proto(sub))
|
92
|
+
end
|
93
|
+
|
94
|
+
def broadcast_unsub_to_routes(sub)
|
95
|
+
opt_max_str = " #{sub.max_responses}" unless sub.max_responses.nil?
|
96
|
+
broadcast_proto_to_routes("UNSUB #{routed_sid(sub)}#{opt_max_str}#{CR_LF}")
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -3,7 +3,7 @@ module NATSD #:nodoc: all
|
|
3
3
|
module Connection #:nodoc: all
|
4
4
|
|
5
5
|
attr_accessor :in_msgs, :out_msgs, :in_bytes, :out_bytes
|
6
|
-
attr_reader :cid, :closing, :last_activity, :writev_size
|
6
|
+
attr_reader :cid, :closing, :last_activity, :writev_size, :subscriptions
|
7
7
|
alias :closing? :closing
|
8
8
|
|
9
9
|
def flush_data
|
@@ -52,7 +52,7 @@ module NATSD #:nodoc: all
|
|
52
52
|
@writev_size = 0
|
53
53
|
@parse_state = AWAITING_CONTROL_LINE
|
54
54
|
send_info
|
55
|
-
debug "
|
55
|
+
debug "#{type} connection created", client_info, cid
|
56
56
|
if Server.ssl_required?
|
57
57
|
debug "Starting TLS/SSL", client_info, cid
|
58
58
|
flush_data
|
@@ -62,7 +62,7 @@ module NATSD #:nodoc: all
|
|
62
62
|
@auth_pending = EM.add_timer(NATSD::Server.auth_timeout) { connect_auth_timeout } if Server.auth_required?
|
63
63
|
@ping_timer = EM.add_periodic_timer(NATSD::Server.ping_interval) { send_ping }
|
64
64
|
@pings_outstanding = 0
|
65
|
-
|
65
|
+
inc_connections
|
66
66
|
return if max_connections_exceeded?
|
67
67
|
end
|
68
68
|
|
@@ -79,12 +79,12 @@ module NATSD #:nodoc: all
|
|
79
79
|
|
80
80
|
def connect_auth_timeout
|
81
81
|
error_close AUTH_REQUIRED
|
82
|
-
debug "
|
82
|
+
debug "#{type} connection timeout due to lack of auth credentials", cid
|
83
83
|
end
|
84
84
|
|
85
85
|
def connect_ssl_timeout
|
86
86
|
error_close SSL_REQUIRED
|
87
|
-
debug "
|
87
|
+
debug "#{type} connection timeout due to lack of TLS/SSL negotiations", cid
|
88
88
|
end
|
89
89
|
|
90
90
|
def receive_data(data)
|
@@ -131,12 +131,12 @@ module NATSD #:nodoc: all
|
|
131
131
|
queue_data(INVALID_SID_NOEXIST) if @pedantic
|
132
132
|
end
|
133
133
|
when PING
|
134
|
-
ctrace('PING OP'
|
134
|
+
ctrace('PING OP') if NATSD::Server.trace_flag?
|
135
135
|
@buf = $'
|
136
136
|
queue_data(PONG_RESPONSE)
|
137
137
|
flush_data
|
138
138
|
when PONG
|
139
|
-
ctrace('PONG OP'
|
139
|
+
ctrace('PONG OP') if NATSD::Server.trace_flag?
|
140
140
|
@buf = $'
|
141
141
|
@pings_outstanding -= 1
|
142
142
|
when CONNECT
|
@@ -149,8 +149,8 @@ module NATSD #:nodoc: all
|
|
149
149
|
queue_data(INVALID_CONFIG)
|
150
150
|
log_error
|
151
151
|
end
|
152
|
-
when
|
153
|
-
ctrace('
|
152
|
+
when INFO_REQ
|
153
|
+
ctrace('INFO_REQUEST OP') if NATSD::Server.trace_flag?
|
154
154
|
return connect_auth_timeout if @auth_pending
|
155
155
|
@buf = $'
|
156
156
|
send_info
|
@@ -196,6 +196,14 @@ module NATSD #:nodoc: all
|
|
196
196
|
queue_data("INFO #{Server.info_string}#{CR_LF}")
|
197
197
|
end
|
198
198
|
|
199
|
+
# Placeholder
|
200
|
+
def process_info(info)
|
201
|
+
end
|
202
|
+
|
203
|
+
def auth_ok?(user, pass)
|
204
|
+
Server.auth_ok?(user, pass)
|
205
|
+
end
|
206
|
+
|
199
207
|
def process_connect_config(config)
|
200
208
|
@verbose = config['verbose'] unless config['verbose'].nil?
|
201
209
|
@pedantic = config['pedantic'] unless config['pedantic'].nil?
|
@@ -203,18 +211,18 @@ module NATSD #:nodoc: all
|
|
203
211
|
return queue_data(OK) unless Server.auth_required?
|
204
212
|
|
205
213
|
EM.cancel_timer(@auth_pending)
|
206
|
-
if
|
214
|
+
if auth_ok?(config['user'], config['pass'])
|
207
215
|
queue_data(OK) if @verbose
|
208
216
|
@auth_pending = nil
|
209
217
|
else
|
210
218
|
error_close AUTH_FAILED
|
211
|
-
debug "Authorization failed for connection", cid
|
219
|
+
debug "Authorization failed for #{type.downcase} connection", cid
|
212
220
|
end
|
213
221
|
end
|
214
222
|
|
215
223
|
def delete_subscriber(sub)
|
216
224
|
ctrace('DELSUB OP', sub.subject, sub.qgroup, sub.sid) if NATSD::Server.trace_flag?
|
217
|
-
Server.unsubscribe(sub)
|
225
|
+
Server.unsubscribe(sub, is_route?)
|
218
226
|
@subscriptions.delete(sub.sid)
|
219
227
|
end
|
220
228
|
|
@@ -235,25 +243,38 @@ module NATSD #:nodoc: all
|
|
235
243
|
debug "Message payload size exceeded (#{sizes}), closing connection"
|
236
244
|
end
|
237
245
|
|
238
|
-
def
|
239
|
-
|
246
|
+
def inc_connections
|
247
|
+
Server.num_connections += 1
|
248
|
+
Server.connections[cid] = self
|
249
|
+
end
|
250
|
+
|
251
|
+
def dec_connections
|
240
252
|
Server.num_connections -= 1
|
241
|
-
|
253
|
+
Server.connections.delete(cid)
|
254
|
+
end
|
255
|
+
|
256
|
+
def process_unbind
|
257
|
+
dec_connections
|
242
258
|
EM.cancel_timer(@ssl_pending) if @ssl_pending
|
243
259
|
@ssl_pending = nil
|
244
260
|
EM.cancel_timer(@auth_pending) if @auth_pending
|
245
261
|
@auth_pending = nil
|
246
262
|
EM.cancel_timer(@ping_timer) if @ping_timer
|
247
263
|
@ping_timer = nil
|
248
|
-
|
264
|
+
@subscriptions.each_value { |sub| Server.unsubscribe(sub) }
|
249
265
|
@closing = true
|
250
266
|
end
|
251
267
|
|
268
|
+
def unbind
|
269
|
+
debug "Client connection closed", client_info, cid
|
270
|
+
process_unbind
|
271
|
+
end
|
272
|
+
|
252
273
|
def ssl_handshake_completed
|
253
274
|
EM.cancel_timer(@ssl_pending)
|
254
275
|
@ssl_pending = nil
|
255
276
|
cert = get_peer_cert
|
256
|
-
debug "
|
277
|
+
debug "#{type} Certificate:", cert ? cert : 'N/A', cid
|
257
278
|
end
|
258
279
|
|
259
280
|
# FIXME! Cert accepted by default
|
@@ -268,6 +289,15 @@ module NATSD #:nodoc: all
|
|
268
289
|
def strip_op(op='')
|
269
290
|
op.dup.sub(CR_LF, EMPTY)
|
270
291
|
end
|
292
|
+
|
293
|
+
def is_route?
|
294
|
+
false
|
295
|
+
end
|
296
|
+
|
297
|
+
def type
|
298
|
+
'Client'
|
299
|
+
end
|
300
|
+
|
271
301
|
end
|
272
302
|
|
273
303
|
end
|
data/lib/nats/server/connz.rb
CHANGED
@@ -33,7 +33,6 @@ module NATSD #:nodoc: all
|
|
33
33
|
|
34
34
|
class Server
|
35
35
|
class << self
|
36
|
-
|
37
36
|
def dump_connections
|
38
37
|
conns, total = [], 0
|
39
38
|
ObjectSpace.each_object(NATSD::Connection) do |c|
|
@@ -47,7 +46,6 @@ module NATSD #:nodoc: all
|
|
47
46
|
:connections => conns
|
48
47
|
}
|
49
48
|
end
|
50
|
-
|
51
49
|
end
|
52
50
|
end
|
53
51
|
|
data/lib/nats/server/const.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
2
|
module NATSD #:nodoc:
|
3
3
|
|
4
|
-
VERSION = '0.
|
4
|
+
VERSION = '0.5.0.beta.12'
|
5
5
|
APP_NAME = 'nats-server'
|
6
6
|
|
7
7
|
DEFAULT_PORT = 4222
|
@@ -12,17 +12,23 @@ module NATSD #:nodoc:
|
|
12
12
|
AWAITING_MSG_PAYLOAD = 2
|
13
13
|
|
14
14
|
# Ops - See protocol.txt for more info
|
15
|
-
INFO = /\AINFO\s
|
15
|
+
INFO = /\AINFO\s*([^\r\n]*)\r\n/i
|
16
16
|
PUB_OP = /\APUB\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i
|
17
|
+
MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i
|
17
18
|
SUB_OP = /\ASUB\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?([^\s]+)\r\n/i
|
18
19
|
UNSUB_OP = /\AUNSUB\s+([^\s]+)\s*(\s+(\d+))?\r\n/i
|
19
20
|
PING = /\APING\s*\r\n/i
|
20
21
|
PONG = /\APONG\s*\r\n/i
|
22
|
+
INFO_REQ = /\AINFO_REQ\s*\r\n/i
|
23
|
+
|
21
24
|
CONNECT = /\ACONNECT\s+([^\r\n]+)\r\n/i
|
22
25
|
UNKNOWN = /\A(.*)\r\n/
|
23
26
|
CTRL_C = /\006/
|
24
27
|
CTRL_D = /\004/
|
25
28
|
|
29
|
+
ERR_RESP = /\A-ERR\s+('.+')?\r\n/i
|
30
|
+
OK_RESP = /\A\+OK\s*\r\n/i #:nodoc:
|
31
|
+
|
26
32
|
# RESPONSES
|
27
33
|
CR_LF = "\r\n".freeze
|
28
34
|
CR_LF_SIZE = CR_LF.bytesize
|
@@ -52,6 +58,9 @@ module NATSD #:nodoc:
|
|
52
58
|
SUB = /^([^\.\*>\s]+|>$|\*)(\.([^\.\*>\s]+|>$|\*))*$/
|
53
59
|
SUB_NO_WC = /^([^\.\*>\s]+)(\.([^\.\*>\s]+))*$/
|
54
60
|
|
61
|
+
# Router Subscription Identifiers
|
62
|
+
RSID = /RSID:(\d+):(\S+)/
|
63
|
+
|
55
64
|
# Some sane default thresholds
|
56
65
|
|
57
66
|
# 1k should be plenty since payloads sans connect string are separate
|
@@ -79,6 +88,9 @@ module NATSD #:nodoc:
|
|
79
88
|
DEFAULT_PING_INTERVAL = 120
|
80
89
|
DEFAULT_PING_MAX = 2
|
81
90
|
|
91
|
+
# Route Reconnect
|
92
|
+
DEFAULT_ROUTE_RECONNECT_INTERVAL = 1.0
|
93
|
+
|
82
94
|
# HTTP
|
83
95
|
RACK_JSON_HDR = { 'Content-Type' => 'application/json' }
|
84
96
|
RACK_TEXT_HDR = { 'Content-Type' => 'text/plain' }
|
data/lib/nats/server/options.rb
CHANGED
@@ -20,6 +20,8 @@ module NATSD
|
|
20
20
|
|
21
21
|
opts.on("-m", "--http_port PORT", "Use HTTP PORT ") { |port| @options[:http_port] = port.to_i }
|
22
22
|
|
23
|
+
opts.on("-r", "--cluster_port PORT", "Use Cluster PORT ") { |port| @options[:cluster_port] = port.to_i }
|
24
|
+
|
23
25
|
opts.on("-c", "--config FILE", "Configuration File") { |file| @options[:config_file] = file }
|
24
26
|
|
25
27
|
opts.separator ""
|
@@ -106,6 +108,21 @@ module NATSD
|
|
106
108
|
@options[:ping_max] = ping['max_outstanding'] if @options[:ping_max].nil?
|
107
109
|
end
|
108
110
|
|
111
|
+
if cluster = config['cluster']
|
112
|
+
@options[:cluster_port] = cluster['port'] if @options[:cluster_port].nil?
|
113
|
+
if auth = cluster['authorization']
|
114
|
+
@options[:cluster_user] = auth['user'] if @options[:cluster_user].nil?
|
115
|
+
@options[:cluster_pass] = auth['password'] if @options[:cluster_pass].nil?
|
116
|
+
@options[:cluster_pass] = auth['pass'] if @options[:cluster_pass].nil?
|
117
|
+
@options[:cluster_token] = auth['token'] if @options[:cluster_token].nil?
|
118
|
+
@options[:cluster_auth_timeout] = auth['timeout'] if @options[:cluster_auth_timeout].nil?
|
119
|
+
@route_auth_required = true
|
120
|
+
end
|
121
|
+
if routes = cluster['routes']
|
122
|
+
@options[:cluster_routes] = routes if @options[:cluster_routes].nil?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
109
126
|
rescue => e
|
110
127
|
log "Could not read configuration file: #{e}"
|
111
128
|
exit 1
|
@@ -120,7 +137,7 @@ module NATSD
|
|
120
137
|
|
121
138
|
def open_syslog
|
122
139
|
return unless @options[:syslog]
|
123
|
-
Syslog.open("#{@options[:syslog]}", Syslog::LOG_PID,
|
140
|
+
Syslog.open("#{@options[:syslog]}", Syslog::LOG_PID, Syslog::LOG_USER) unless Syslog.opened?
|
124
141
|
end
|
125
142
|
|
126
143
|
def close_syslog
|
@@ -173,7 +190,7 @@ module NATSD
|
|
173
190
|
|
174
191
|
@auth_required = (not @options[:user].nil?)
|
175
192
|
|
176
|
-
@ssl_required =
|
193
|
+
@ssl_required = @options[:ssl]
|
177
194
|
|
178
195
|
# Pings
|
179
196
|
@options[:ping_interval] ||= DEFAULT_PING_INTERVAL
|
@@ -0,0 +1,279 @@
|
|
1
|
+
module NATSD #:nodoc: all
|
2
|
+
|
3
|
+
# Need to make this a class with EM > 1.0
|
4
|
+
class Route < EventMachine::Connection #:nodoc:
|
5
|
+
|
6
|
+
include Connection
|
7
|
+
|
8
|
+
attr_reader :rid, :remote_rid, :closing, :r_obj, :reconnecting
|
9
|
+
alias :peer_info :client_info
|
10
|
+
alias :reconnecting? :reconnecting
|
11
|
+
|
12
|
+
def initialize(route=nil)
|
13
|
+
@r_obj = route
|
14
|
+
end
|
15
|
+
|
16
|
+
def solicited?
|
17
|
+
r_obj != nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def connection_completed
|
21
|
+
debug "Route connected", rid
|
22
|
+
return unless reconnecting?
|
23
|
+
|
24
|
+
# Kill reconnect if we got here from there
|
25
|
+
cancel_reconnect
|
26
|
+
@buf, @closing = nil, false
|
27
|
+
post_init
|
28
|
+
end
|
29
|
+
|
30
|
+
def post_init
|
31
|
+
@rid = Server.rid
|
32
|
+
@subscriptions = {}
|
33
|
+
@in_msgs = @out_msgs = @in_bytes = @out_bytes = 0
|
34
|
+
@writev_size = 0
|
35
|
+
@parse_state = AWAITING_CONTROL_LINE
|
36
|
+
|
37
|
+
# Queue up auth if needed and we solicited the connection
|
38
|
+
debug "Route connection created", peer_info, rid
|
39
|
+
|
40
|
+
# queue up auth if needed and we solicited the connection
|
41
|
+
if solicited?
|
42
|
+
debug "Route sent authorization", rid
|
43
|
+
send_auth
|
44
|
+
else
|
45
|
+
# FIXME, separate variables for timeout?
|
46
|
+
@auth_pending = EM.add_timer(NATSD::Server.auth_timeout) { connect_auth_timeout } if Server.route_auth_required?
|
47
|
+
end
|
48
|
+
|
49
|
+
send_info
|
50
|
+
@ping_timer = EM.add_periodic_timer(NATSD::Server.ping_interval) { send_ping }
|
51
|
+
@pings_outstanding = 0
|
52
|
+
inc_connections
|
53
|
+
send_local_subs_to_route
|
54
|
+
end
|
55
|
+
|
56
|
+
# TODO: Make sure max_requested is also propogated on reconnect
|
57
|
+
def send_local_subs_to_route
|
58
|
+
ObjectSpace.each_object(NATSD::Connection) do |c|
|
59
|
+
next if c.closing? || c.type != 'Client'
|
60
|
+
c.subscriptions.each_value do |sub|
|
61
|
+
queue_data(NATSD::Server.route_sub_proto(sub))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def process_connect_route_config(config)
|
67
|
+
@verbose = config['verbose'] unless config['verbose'].nil?
|
68
|
+
@pedantic = config['pedantic'] unless config['pedantic'].nil?
|
69
|
+
|
70
|
+
return queue_data(OK) unless Server.route_auth_required?
|
71
|
+
|
72
|
+
EM.cancel_timer(@auth_pending)
|
73
|
+
if auth_ok?(config['user'], config['pass'])
|
74
|
+
debug "Route received proper credentials", rid
|
75
|
+
queue_data(OK) if @verbose
|
76
|
+
@auth_pending = nil
|
77
|
+
else
|
78
|
+
error_close AUTH_FAILED
|
79
|
+
debug "Authorization failed for #{type.downcase} connection", rid
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def connect_auth_timeout
|
84
|
+
error_close AUTH_REQUIRED
|
85
|
+
debug "#{type} connection timeout due to lack of auth credentials", rid
|
86
|
+
end
|
87
|
+
|
88
|
+
def receive_data(data)
|
89
|
+
@buf = @buf ? @buf << data : data
|
90
|
+
return close_connection if @buf =~ /(\006|\004)/ # ctrl+c or ctrl+d for telnet friendly
|
91
|
+
|
92
|
+
while (@buf && !@closing)
|
93
|
+
case @parse_state
|
94
|
+
when AWAITING_CONTROL_LINE
|
95
|
+
case @buf
|
96
|
+
when MSG
|
97
|
+
ctrace('MSG OP', strip_op($&)) if NATSD::Server.trace_flag?
|
98
|
+
return connect_auth_timeout if @auth_pending
|
99
|
+
@buf = $'
|
100
|
+
@parse_state = AWAITING_MSG_PAYLOAD
|
101
|
+
@msg_sub, @msg_sid, @msg_reply, @msg_size = $1, $2, $4, $5.to_i
|
102
|
+
if (@msg_size > NATSD::Server.max_payload)
|
103
|
+
debug_print_msg_too_big(@msg_size)
|
104
|
+
error_close PAYLOAD_TOO_BIG
|
105
|
+
end
|
106
|
+
queue_data(INVALID_SUBJECT) if (@pedantic && !(@msg_sub =~ SUB_NO_WC))
|
107
|
+
when SUB_OP
|
108
|
+
ctrace('SUB OP', strip_op($&)) if NATSD::Server.trace_flag?
|
109
|
+
return connect_auth_timeout if @auth_pending
|
110
|
+
@buf = $'
|
111
|
+
sub, qgroup, sid = $1, $3, $4
|
112
|
+
return queue_data(INVALID_SUBJECT) if !($1 =~ SUB)
|
113
|
+
return queue_data(INVALID_SID_TAKEN) if @subscriptions[sid]
|
114
|
+
sub = Subscriber.new(self, sub, sid, qgroup, 0)
|
115
|
+
@subscriptions[sid] = sub
|
116
|
+
Server.subscribe(sub, is_route?)
|
117
|
+
queue_data(OK) if @verbose
|
118
|
+
when UNSUB_OP
|
119
|
+
ctrace('UNSUB OP', strip_op($&)) if NATSD::Server.trace_flag?
|
120
|
+
return connect_auth_timeout if @auth_pending
|
121
|
+
@buf = $'
|
122
|
+
sid, sub = $1, @subscriptions[$1]
|
123
|
+
if sub
|
124
|
+
# If we have set max_responses, we will unsubscribe once we have received
|
125
|
+
# the appropriate amount of responses.
|
126
|
+
sub.max_responses = ($2 && $3) ? $3.to_i : nil
|
127
|
+
delete_subscriber(sub) unless (sub.max_responses && (sub.num_responses < sub.max_responses))
|
128
|
+
queue_data(OK) if @verbose
|
129
|
+
else
|
130
|
+
queue_data(INVALID_SID_NOEXIST) if @pedantic
|
131
|
+
end
|
132
|
+
when PING
|
133
|
+
ctrace('PING OP') if NATSD::Server.trace_flag?
|
134
|
+
@buf = $'
|
135
|
+
queue_data(PONG_RESPONSE)
|
136
|
+
flush_data
|
137
|
+
when PONG
|
138
|
+
ctrace('PONG OP') if NATSD::Server.trace_flag?
|
139
|
+
@buf = $'
|
140
|
+
@pings_outstanding -= 1
|
141
|
+
when CONNECT
|
142
|
+
ctrace('CONNECT OP', strip_op($&)) if NATSD::Server.trace_flag?
|
143
|
+
@buf = $'
|
144
|
+
begin
|
145
|
+
config = JSON.parse($1)
|
146
|
+
process_connect_route_config(config)
|
147
|
+
rescue => e
|
148
|
+
queue_data(INVALID_CONFIG)
|
149
|
+
log_error
|
150
|
+
end
|
151
|
+
when INFO_REQ
|
152
|
+
ctrace('INFO_REQUEST OP') if NATSD::Server.trace_flag?
|
153
|
+
return connect_auth_timeout if @auth_pending
|
154
|
+
@buf = $'
|
155
|
+
send_info
|
156
|
+
when INFO
|
157
|
+
ctrace('INFO OP', strip_op($&)) if NATSD::Server.trace_flag?
|
158
|
+
return connect_auth_timeout if @auth_pending
|
159
|
+
@buf = $'
|
160
|
+
process_info($1)
|
161
|
+
when ERR_RESP
|
162
|
+
ctrace('-ERR', $1) if NATSD::Server.trace_flag?
|
163
|
+
close_connection
|
164
|
+
exit
|
165
|
+
when OK_RESP
|
166
|
+
ctrace('+OK') if NATSD::Server.trace_flag?
|
167
|
+
@buf = $'
|
168
|
+
when UNKNOWN
|
169
|
+
ctrace('Unknown Op', strip_op($&)) if NATSD::Server.trace_flag?
|
170
|
+
return connect_auth_timeout if @auth_pending
|
171
|
+
@buf = $'
|
172
|
+
queue_data(UNKNOWN_OP)
|
173
|
+
else
|
174
|
+
# If we are here we do not have a complete line yet that we understand.
|
175
|
+
# If too big, cut the connection off.
|
176
|
+
if @buf.bytesize > NATSD::Server.max_control_line
|
177
|
+
debug_print_controlline_too_big(@buf.bytesize)
|
178
|
+
close_connection
|
179
|
+
end
|
180
|
+
return
|
181
|
+
end
|
182
|
+
@buf = nil if (@buf && @buf.empty?)
|
183
|
+
|
184
|
+
when AWAITING_MSG_PAYLOAD
|
185
|
+
return unless (@buf.bytesize >= (@msg_size + CR_LF_SIZE))
|
186
|
+
msg = @buf.slice(0, @msg_size)
|
187
|
+
|
188
|
+
ctrace('Processing routed msg', @msg_sub, @msg_reply, msg) if NATSD::Server.trace_flag?
|
189
|
+
queue_data(OK) if @verbose
|
190
|
+
|
191
|
+
# We deliver normal subscriptions like a client publish, which
|
192
|
+
# eliminates the duplicate traversal over the route. However,
|
193
|
+
# qgroups are sent individually per group for only the route
|
194
|
+
# with the intended subscriber, since route interest is L2
|
195
|
+
# semantics, we deliver those direct.
|
196
|
+
if (sub = Server.rsid_qsub(@msg_sid))
|
197
|
+
# Allows nil reply to not have extra space
|
198
|
+
reply = @msg_reply + ' ' if @msg_reply
|
199
|
+
Server.deliver_to_subscriber(sub, @msg_sub, reply, msg)
|
200
|
+
else
|
201
|
+
Server.route_to_subscribers(@msg_sub, @msg_reply, msg, is_route?)
|
202
|
+
end
|
203
|
+
|
204
|
+
@in_msgs += 1
|
205
|
+
@in_bytes += @msg_size
|
206
|
+
@buf = @buf.slice((@msg_size + CR_LF_SIZE), @buf.bytesize)
|
207
|
+
@msg_sub = @msg_size = @reply = nil
|
208
|
+
@parse_state = AWAITING_CONTROL_LINE
|
209
|
+
@buf = nil if (@buf && @buf.empty?)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def send_auth
|
215
|
+
return unless r_obj[:uri].user
|
216
|
+
cs = { :user => r_obj[:uri].user, :pass => r_obj[:uri].password }
|
217
|
+
queue_data("CONNECT #{cs.to_json}#{CR_LF}")
|
218
|
+
end
|
219
|
+
|
220
|
+
def send_info
|
221
|
+
queue_data("INFO #{Server.route_info_string}#{CR_LF}")
|
222
|
+
end
|
223
|
+
|
224
|
+
def process_info(info_json)
|
225
|
+
info = JSON.parse(info_json)
|
226
|
+
@remote_rid = info['server_id'] unless info['server_id'].nil?
|
227
|
+
super(info_json)
|
228
|
+
end
|
229
|
+
|
230
|
+
def auth_ok?(user, pass)
|
231
|
+
Server.route_auth_ok?(user, pass)
|
232
|
+
end
|
233
|
+
|
234
|
+
def inc_connections
|
235
|
+
Server.num_routes += 1
|
236
|
+
Server.add_route(self)
|
237
|
+
end
|
238
|
+
|
239
|
+
def dec_connections
|
240
|
+
Server.num_routes -= 1
|
241
|
+
Server.remove_route(self)
|
242
|
+
end
|
243
|
+
|
244
|
+
def try_reconnect
|
245
|
+
debug "Trying to reconnect route", peer_info, rid
|
246
|
+
EM.reconnect(r_obj[:uri].host, r_obj[:uri].port, self)
|
247
|
+
end
|
248
|
+
|
249
|
+
def cancel_reconnect
|
250
|
+
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
|
251
|
+
@reconnect_timer = nil
|
252
|
+
@reconnecting = false
|
253
|
+
end
|
254
|
+
|
255
|
+
def unbind
|
256
|
+
return if reconnecting?
|
257
|
+
debug "Route connection closed", peer_info, rid
|
258
|
+
process_unbind
|
259
|
+
if solicited?
|
260
|
+
@reconnecting = true
|
261
|
+
@reconnect_timer = EM.add_periodic_timer(NATSD::DEFAULT_ROUTE_RECONNECT_INTERVAL) { try_reconnect }
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def ctrace(*args)
|
266
|
+
trace(args, "r: #{rid}")
|
267
|
+
end
|
268
|
+
|
269
|
+
def is_route?
|
270
|
+
true
|
271
|
+
end
|
272
|
+
|
273
|
+
def type
|
274
|
+
'Route'
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|