nats 0.4.10 → 0.4.22
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.md +42 -0
- data/README.md +68 -56
- data/Rakefile +1 -1
- data/bin/nats-request +41 -0
- data/bin/nats-top +146 -0
- data/lib/nats/client.rb +190 -74
- data/lib/nats/server.rb +15 -0
- data/lib/nats/server/connection.rb +269 -0
- data/lib/nats/server/connz.rb +54 -0
- data/lib/nats/server/const.rb +24 -2
- data/lib/nats/server/options.rb +64 -3
- data/lib/nats/server/server.rb +73 -172
- data/lib/nats/server/sublist.rb +67 -16
- data/lib/nats/server/util.rb +45 -2
- data/lib/nats/server/varz.rb +35 -0
- data/nats.gemspec +12 -6
- metadata +64 -55
- data/ChangeLog +0 -11
data/HISTORY.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# HISTORY
|
2
|
+
|
3
|
+
## v0.4.22 (Mar 5, 2012)
|
4
|
+
|
5
|
+
- HTTP based server monitoring (/varz, /connz, /healthz)
|
6
|
+
- Perfomance and Stability improvements
|
7
|
+
- Client monitoring
|
8
|
+
- Server to Client pings
|
9
|
+
- Multiple Auth users
|
10
|
+
- SSL/TSL support
|
11
|
+
- nats-top utility
|
12
|
+
- Connection state dump on SIGUSR2
|
13
|
+
- Client Server information support
|
14
|
+
- Client Fast Producer support
|
15
|
+
- Client reconenct callbacks
|
16
|
+
- Server Max Connections support
|
17
|
+
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.10...v0.4.22
|
18
|
+
|
19
|
+
## v0.4.10 (Apr 21, 2011)
|
20
|
+
|
21
|
+
- Minor bug fixes
|
22
|
+
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.8...v0.4.10
|
23
|
+
|
24
|
+
## v0.4.8 (Apr 2, 2011)
|
25
|
+
|
26
|
+
- Minor bug fixes
|
27
|
+
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.2...v0.4.8
|
28
|
+
|
29
|
+
## v0.4.2 (Feb 21, 2011)
|
30
|
+
|
31
|
+
- Queue group support
|
32
|
+
- Auto-unsubscribe support
|
33
|
+
- Time expiration on subscriptions
|
34
|
+
- Jruby initial support
|
35
|
+
- Performance enhancements
|
36
|
+
- Complete config file support
|
37
|
+
- See full list @ https://github.com/derekcollison/nats/compare/v0.3.12...v0.4.2
|
38
|
+
|
39
|
+
## v0.3.12 (Nov 21, 2010)
|
40
|
+
|
41
|
+
- Initial Release
|
42
|
+
|
data/README.md
CHANGED
@@ -1,102 +1,114 @@
|
|
1
1
|
# NATS
|
2
2
|
|
3
|
-
A lightweight
|
3
|
+
A lightweight publish-subscribe and distributed queueing messaging system.
|
4
|
+
|
5
|
+
[![Build Status](https://secure.travis-ci.org/derekcollison/nats.png)](http://travis-ci.org/derekcollison/nats)
|
4
6
|
|
5
7
|
## Supported Platforms
|
6
8
|
|
7
9
|
This gem currently works on the following Ruby platforms:
|
8
10
|
|
9
|
-
- MRI 1.8 and 1.9 (Performance is best on 1.9.
|
11
|
+
- MRI 1.8 and 1.9 (Performance is best on 1.9.3)
|
10
12
|
- Rubinius
|
11
13
|
- JRuby
|
12
14
|
|
15
|
+
There is a [Node.js](https://github.com/derekcollison/node_nats) client available as well.
|
16
|
+
|
13
17
|
## Getting Started
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
```bash
|
20
|
+
[sudo] gem install nats
|
21
|
+
== or ==
|
22
|
+
[sudo] rake geminstall
|
18
23
|
|
19
|
-
|
20
|
-
|
24
|
+
nats-sub foo &
|
25
|
+
nats-pub foo 'Hello World!'
|
26
|
+
```
|
21
27
|
|
22
|
-
## Usage
|
28
|
+
## Basic Usage
|
23
29
|
|
24
|
-
|
30
|
+
```ruby
|
31
|
+
require "nats/client"
|
25
32
|
|
26
|
-
|
33
|
+
NATS.start do
|
27
34
|
|
28
|
-
|
29
|
-
|
35
|
+
# Simple Subscriber
|
36
|
+
NATS.subscribe('foo') { |msg| puts "Msg received : '#{msg}'" }
|
30
37
|
|
31
|
-
|
32
|
-
|
38
|
+
# Simple Publisher
|
39
|
+
NATS.publish('foo.bar.baz', 'Hello World!')
|
33
40
|
|
34
|
-
|
35
|
-
|
36
|
-
|
41
|
+
# Unsubscribing
|
42
|
+
sid = NATS.subscribe('bar') { |msg| puts "Msg received : '#{msg}'" }
|
43
|
+
NATS.unsubscribe(sid)
|
37
44
|
|
38
|
-
|
39
|
-
|
45
|
+
# Requests
|
46
|
+
NATS.request('help') { |response| puts "Got a response: '#{response}'" }
|
40
47
|
|
41
|
-
|
42
|
-
|
48
|
+
# Replies
|
49
|
+
NATS.subscribe('help') { |msg, reply| NATS.publish(reply, "I'll help!") }
|
43
50
|
|
44
|
-
|
45
|
-
|
51
|
+
# Stop using NATS.stop, exits EM loop if NATS.start started the loop
|
52
|
+
NATS.stop
|
46
53
|
|
47
|
-
|
54
|
+
end
|
55
|
+
```
|
48
56
|
|
49
57
|
## Wildcard Subscriptions
|
50
58
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
59
|
+
```ruby
|
60
|
+
# "*" matches any token, at any level of the subject.
|
61
|
+
NATS.subscribe('foo.*.baz') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
62
|
+
NATS.subscribe('foo.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
63
|
+
NATS.subscribe('*.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
55
64
|
|
56
|
-
|
57
|
-
|
58
|
-
|
65
|
+
# ">" matches any length of the tail of a subject and can only be the last token
|
66
|
+
# E.g. 'foo.>' will match 'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22'
|
67
|
+
NATS.subscribe('foo.>') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
68
|
+
```
|
59
69
|
|
60
70
|
## Queues Groups
|
61
71
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
72
|
+
```ruby
|
73
|
+
# All subscriptions with the same queue name will form a queue group
|
74
|
+
# Each message will be delivered to only one subscriber per queue group, queuing semantics
|
75
|
+
# You can have as many queue groups as you wish
|
76
|
+
# Normal subscribers will continue to work as expected.
|
77
|
+
NATS.subscribe(subject, :queue => 'job.workers') { |msg| puts "Received '#{msg}'" }
|
78
|
+
```
|
67
79
|
|
68
80
|
## Advanced Usage
|
81
|
+
```ruby
|
82
|
+
# Publish with closure, callback fires when server has processed the message
|
83
|
+
NATS.publish('foo', 'You done?') { puts 'msg processed!' }
|
69
84
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
# Timeouts for subscriptions
|
74
|
-
sid = NATS.subscribe('foo') { received += 1 }
|
75
|
-
NATS.timeout(sid, TIMEOUT_IN_SECS) { timeout_recvd = true }
|
76
|
-
|
77
|
-
# Timeout unless a certain number of messages have been received
|
78
|
-
NATS.timeout(sid, TIMEOUT_IN_SECS, :expected => 2) { timeout_recvd = true }
|
85
|
+
# Timeouts for subscriptions
|
86
|
+
sid = NATS.subscribe('foo') { received += 1 }
|
87
|
+
NATS.timeout(sid, TIMEOUT_IN_SECS) { timeout_recvd = true }
|
79
88
|
|
80
|
-
|
81
|
-
|
89
|
+
# Timeout unless a certain number of messages have been received
|
90
|
+
NATS.timeout(sid, TIMEOUT_IN_SECS, :expected => 2) { timeout_recvd = true }
|
82
91
|
|
83
|
-
|
84
|
-
|
85
|
-
puts "received msg"
|
86
|
-
NATS.stop
|
87
|
-
end
|
92
|
+
# Auto-unsubscribe after MAX_WANTED messages received
|
93
|
+
NATS.unsubscribe(sid, MAX_WANTED)
|
88
94
|
|
89
|
-
|
90
|
-
|
95
|
+
# Multiple connections
|
96
|
+
NATS.subscribe('test') do |msg|
|
97
|
+
puts "received msg"
|
98
|
+
NATS.stop
|
99
|
+
end
|
91
100
|
|
101
|
+
# Form second connection to send message on
|
102
|
+
NATS.connect { NATS.publish('test', 'Hello World!') }
|
103
|
+
```
|
92
104
|
|
93
|
-
See examples and
|
105
|
+
See examples and benchmarks for more information..
|
94
106
|
|
95
107
|
## License
|
96
108
|
|
97
109
|
(The MIT License)
|
98
110
|
|
99
|
-
Copyright (c) 2010
|
111
|
+
Copyright (c) 2010-2012 Derek Collison
|
100
112
|
|
101
113
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
102
114
|
of this software and associated documentation files (the "Software"), to
|
data/Rakefile
CHANGED
data/bin/nats-request
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'nats/client'
|
6
|
+
|
7
|
+
['TERM', 'INT'].each { |s| trap(s) { puts; exit! } }
|
8
|
+
|
9
|
+
def usage
|
10
|
+
puts "Usage: nats-request <subject> <msg> [-s server] [-t] [-n responses]"; exit
|
11
|
+
end
|
12
|
+
|
13
|
+
args = ARGV.dup
|
14
|
+
opts_parser = OptionParser.new do |opts|
|
15
|
+
opts.on('-s SERVER') { |server| $nats_server = server }
|
16
|
+
opts.on('-t') { $show_time = true }
|
17
|
+
opts.on('-n RESPONSES') { |responses| $responses = Integer(responses) if Integer(responses) > 0 }
|
18
|
+
end
|
19
|
+
args = opts_parser.parse!(args)
|
20
|
+
|
21
|
+
subject, msg = args
|
22
|
+
usage unless subject
|
23
|
+
msg ||= 'Hello World'
|
24
|
+
|
25
|
+
def time_prefix
|
26
|
+
"[#{Time.now}] " if $show_time
|
27
|
+
end
|
28
|
+
|
29
|
+
def header
|
30
|
+
$i=0 unless $i
|
31
|
+
"#{time_prefix}[\##{$i+=1}]"
|
32
|
+
end
|
33
|
+
|
34
|
+
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
|
35
|
+
|
36
|
+
NATS.start(:uri => $nats_server, :autostart => true) do
|
37
|
+
NATS.request(subject, msg) { |(msg, reply)|
|
38
|
+
puts "#{header} Replied with : '#{msg}'"
|
39
|
+
exit! if $responses && ($responses-=1) < 1
|
40
|
+
}
|
41
|
+
end
|
data/bin/nats-top
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'net/http'
|
5
|
+
require 'uri'
|
6
|
+
require 'io/wait'
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'json'
|
10
|
+
|
11
|
+
def usage
|
12
|
+
puts "Usage: nats-top [-s server_uri] [-m local monitor port] [-n num_connections] [-d delay_secs] [--sort sort_by]"
|
13
|
+
puts "--sort_options for more help"
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
|
17
|
+
$valid_sort_options = ['pending_size', 'msgs_to', 'msgs_from', 'bytes_to', 'bytes_from', 'subs']
|
18
|
+
|
19
|
+
def sort_options_help
|
20
|
+
puts "Available sort_by options: #{$valid_sort_options.join(', ')}."
|
21
|
+
puts "E.g. #{$0} -s bytes_to"
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
args = ARGV.dup
|
26
|
+
opts_parser = OptionParser.new do |opts|
|
27
|
+
opts.on('-s server_uri') { |server| $nats_server = server }
|
28
|
+
opts.on('-m local_port') { |port| $nats_port = port.to_i }
|
29
|
+
opts.on('-n num_connections') { |num| $num_connections = num.to_i }
|
30
|
+
opts.on('-d delay') { |delay| $delay = delay.to_f }
|
31
|
+
opts.on('--sort sort_by') { |sort_key| $sort_key = sort_key }
|
32
|
+
opts.on('--sort_options') { sort_options_help }
|
33
|
+
opts.on('-h') { usage }
|
34
|
+
opts.on('--help') { usage }
|
35
|
+
end
|
36
|
+
args = opts_parser.parse!(args)
|
37
|
+
|
38
|
+
DEFAULT_MONITOR_PORT = 9222
|
39
|
+
DEFAULT_NUM_CONNECTIONS = 10
|
40
|
+
DEFAULT_DELAY = 1 #sec
|
41
|
+
DEFAULT_SORT = 'pending_size'
|
42
|
+
|
43
|
+
$nats_port = DEFAULT_MONITOR_PORT if $nats_port.nil?
|
44
|
+
$num_connections = DEFAULT_NUM_CONNECTIONS if $num_connections.nil?
|
45
|
+
$nats_server = "http://localhost:#{$nats_port}" if $nats_server.nil?
|
46
|
+
|
47
|
+
$nats_server = "http://#{$nats_server}" unless $nats_server.start_with?('http')
|
48
|
+
|
49
|
+
$delay = DEFAULT_DELAY if $delay.nil?
|
50
|
+
$sort_key = DEFAULT_SORT if $sort_key.nil?
|
51
|
+
$sort_key.downcase!
|
52
|
+
|
53
|
+
unless $valid_sort_options.include?($sort_key)
|
54
|
+
puts "Invalid sort_by argument: #{$sort_key}"
|
55
|
+
sort_options_help
|
56
|
+
end
|
57
|
+
|
58
|
+
varz_uri = URI.parse("#{$nats_server}/varz")
|
59
|
+
connz_uri = URI.parse("#{$nats_server}/connz?n=#{$num_connections}&s=#{$sort_key}")
|
60
|
+
|
61
|
+
def psize(size, prec=1)
|
62
|
+
return 'NA' unless size
|
63
|
+
return sprintf("%.#{prec}f", size) if size < 1024
|
64
|
+
return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
|
65
|
+
return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
|
66
|
+
return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
|
67
|
+
end
|
68
|
+
|
69
|
+
def clear_screen
|
70
|
+
print "\e[H\e[2J"
|
71
|
+
end
|
72
|
+
|
73
|
+
['TERM', 'INT'].each { |s| trap(s) { clear_screen; exit! } }
|
74
|
+
|
75
|
+
in_last_msgs = in_last_bytes = 0
|
76
|
+
out_last_msgs = out_last_bytes = 0
|
77
|
+
|
78
|
+
poll = Time.now
|
79
|
+
first = true
|
80
|
+
|
81
|
+
while true
|
82
|
+
begin
|
83
|
+
|
84
|
+
varz_response = Net::HTTP::get_response(varz_uri)
|
85
|
+
varz = JSON.parse(varz_response.body, :symbolize_keys => true, :symbolize_names => true)
|
86
|
+
|
87
|
+
# Simple rates
|
88
|
+
delta_in_msgs, in_last_msgs = varz[:in_msgs] - in_last_msgs, varz[:in_msgs]
|
89
|
+
delta_in_bytes, in_last_bytes = varz[:in_bytes] - in_last_bytes, varz[:in_bytes]
|
90
|
+
delta_out_msgs, out_last_msgs = varz[:out_msgs] - out_last_msgs, varz[:out_msgs]
|
91
|
+
delta_out_bytes, out_last_bytes = varz[:out_bytes] - out_last_bytes, varz[:out_bytes]
|
92
|
+
|
93
|
+
now = Time.now
|
94
|
+
tdelta, poll = now - poll, now
|
95
|
+
|
96
|
+
unless first
|
97
|
+
rate_in_msgs = delta_in_msgs / tdelta
|
98
|
+
rate_in_bytes = delta_in_bytes / tdelta
|
99
|
+
rate_out_msgs = delta_out_msgs / tdelta
|
100
|
+
rate_out_bytes = delta_out_bytes / tdelta
|
101
|
+
end
|
102
|
+
|
103
|
+
connz_response = Net::HTTP::get_response(connz_uri)
|
104
|
+
connz = JSON.parse(connz_response.body, :symbolize_keys => true, :symbolize_names => true)
|
105
|
+
|
106
|
+
clear_screen
|
107
|
+
|
108
|
+
puts "\nServer:"
|
109
|
+
puts " Load: CPU: #{varz[:cpu]}% Memory: #{psize(varz[:mem])}"
|
110
|
+
print " In: Msgs: #{psize(varz[:in_msgs])} Bytes: #{psize(varz[:in_bytes])}"
|
111
|
+
puts " Msgs/Sec: #{psize(rate_in_msgs)} Bytes/Sec: #{psize(rate_in_bytes)}"
|
112
|
+
|
113
|
+
print " Out: Msgs: #{psize(varz[:out_msgs])} Bytes: #{psize(varz[:out_bytes])}"
|
114
|
+
puts " Msgs/Sec: #{psize(rate_out_msgs)} Bytes/Sec: #{psize(rate_out_bytes)}"
|
115
|
+
|
116
|
+
puts "\nConnections: #{psize(connz[:num_connections], 0)}"
|
117
|
+
|
118
|
+
conn_t = " %-20s %-8s %-6s %-10s %-10s %-10s %-10s %-10s\n"
|
119
|
+
printf(conn_t, 'HOST', 'CID', 'SUBS', 'PENDING', 'MSGS_TO', 'MSGS_FROM', 'BYTES_TO', 'BYTES_FROM')
|
120
|
+
|
121
|
+
connz[:connections].each do |conn|
|
122
|
+
printf(conn_t, "#{conn[:ip]}:#{conn[:port]}",
|
123
|
+
conn[:cid],
|
124
|
+
psize(conn[:subscriptions]),
|
125
|
+
psize(conn[:pending_size]),
|
126
|
+
psize(conn[:out_msgs]),
|
127
|
+
psize(conn[:in_msgs]),
|
128
|
+
psize(conn[:out_bytes]),
|
129
|
+
psize(conn[:in_bytes])
|
130
|
+
)
|
131
|
+
end
|
132
|
+
puts
|
133
|
+
|
134
|
+
first = false
|
135
|
+
|
136
|
+
sleep($delay)
|
137
|
+
|
138
|
+
rescue => e
|
139
|
+
puts "Error: #{e}"
|
140
|
+
exit(1)
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
|
data/lib/nats/client.rb
CHANGED
@@ -8,7 +8,7 @@ require "#{ep}/ext/json"
|
|
8
8
|
|
9
9
|
module NATS
|
10
10
|
|
11
|
-
VERSION =
|
11
|
+
VERSION = '0.4.22'.freeze
|
12
12
|
|
13
13
|
DEFAULT_PORT = 4222
|
14
14
|
DEFAULT_URI = "nats://localhost:#{DEFAULT_PORT}".freeze
|
@@ -16,6 +16,11 @@ module NATS
|
|
16
16
|
MAX_RECONNECT_ATTEMPTS = 10
|
17
17
|
RECONNECT_TIME_WAIT = 2
|
18
18
|
|
19
|
+
MAX_PENDING_SIZE = 32768
|
20
|
+
|
21
|
+
# Maximum outbound size per client to trigger FP, 20MB
|
22
|
+
FAST_PRODUCER_THRESHOLD = (10*1024*1024)
|
23
|
+
|
19
24
|
# Protocol
|
20
25
|
# @private
|
21
26
|
MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i #:nodoc:
|
@@ -50,19 +55,20 @@ module NATS
|
|
50
55
|
# Duplicate autostart protection
|
51
56
|
@@tried_autostart = {}
|
52
57
|
|
53
|
-
class Error < StandardError #:nodoc:
|
54
|
-
end
|
58
|
+
class Error < StandardError; end #:nodoc:
|
55
59
|
|
56
60
|
# When the NATS server sends us an ERROR message, this is raised/passed by default
|
57
|
-
class ServerError < Error #:nodoc:
|
58
|
-
|
61
|
+
class ServerError < Error; end #:nodoc:
|
62
|
+
|
63
|
+
# When we detect error on the client side (e.g. Fast Producer)
|
64
|
+
class ClientError < Error; end #:nodoc:
|
59
65
|
|
60
66
|
# When we cannot connect to the server (either initially or after a reconnect), this is raised/passed
|
61
|
-
class ConnectError < Error #:nodoc:
|
62
|
-
end
|
67
|
+
class ConnectError < Error; end #:nodoc:
|
63
68
|
|
64
69
|
class << self
|
65
70
|
attr_reader :client, :reactor_was_running, :err_cb, :err_cb_overridden #:nodoc:
|
71
|
+
attr_reader :reconnect_cb #:nodoc
|
66
72
|
attr_accessor :timeout_cb #:nodoc
|
67
73
|
|
68
74
|
alias :reactor_was_running? :reactor_was_running
|
@@ -72,12 +78,15 @@ module NATS
|
|
72
78
|
# The optional block will be called when the connection has been completed.
|
73
79
|
#
|
74
80
|
# @param [Hash] opts
|
75
|
-
# @option opts [String] :uri The URI to connect to, example nats://localhost:4222
|
81
|
+
# @option opts [String|URI] :uri The URI to connect to, example nats://localhost:4222
|
76
82
|
# @option opts [Boolean] :autostart Boolean that can be used to engage server autostart functionality.
|
77
83
|
# @option opts [Boolean] :reconnect Boolean that can be used to suppress reconnect functionality.
|
78
84
|
# @option opts [Boolean] :debug Boolean that can be used to output additional debug information.
|
79
85
|
# @option opts [Boolean] :verbose Boolean that is sent to server for setting verbose protocol mode.
|
80
86
|
# @option opts [Boolean] :pedantic Boolean that is sent to server for setting pedantic mode.
|
87
|
+
# @option opts [Boolean] :ssl Boolean that is sent to server for setting TLS/SSL mode.
|
88
|
+
# @option opts [Integer] :max_reconnect_attempts Integer that can be used to set the max number of reconnect tries
|
89
|
+
# @option opts [Integer] :reconnect_time_wait Integer that can be used to set the number of seconds to wait between reconnect tries
|
81
90
|
# @param [Block] &blk called when the connection is completed. Connection will be passed to the block.
|
82
91
|
# @return [NATS] connection to the server.
|
83
92
|
def connect(opts={}, &blk)
|
@@ -85,14 +94,22 @@ module NATS
|
|
85
94
|
opts[:verbose] = false if opts[:verbose].nil?
|
86
95
|
opts[:pedantic] = false if opts[:pedantic].nil?
|
87
96
|
opts[:reconnect] = true if opts[:reconnect].nil?
|
97
|
+
opts[:ssl] = false if opts[:ssl].nil?
|
98
|
+
opts[:max_reconnect_attempts] = MAX_RECONNECT_ATTEMPTS if opts[:max_reconnect_attempts].nil?
|
99
|
+
opts[:reconnect_time_wait] = RECONNECT_TIME_WAIT if opts[:reconnect_time_wait].nil?
|
88
100
|
|
89
101
|
# Override with ENV
|
90
102
|
opts[:uri] ||= ENV['NATS_URI'] || DEFAULT_URI
|
91
|
-
opts[:verbose] = ENV['NATS_VERBOSE'] unless ENV['NATS_VERBOSE'].nil?
|
92
|
-
opts[:pedantic] = ENV['NATS_PEDANTIC'] unless ENV['NATS_PEDANTIC'].nil?
|
93
|
-
opts[:debug] = ENV['NATS_DEBUG']
|
94
|
-
|
95
|
-
|
103
|
+
opts[:verbose] = ENV['NATS_VERBOSE'].downcase == 'true' unless ENV['NATS_VERBOSE'].nil?
|
104
|
+
opts[:pedantic] = ENV['NATS_PEDANTIC'].downcase == 'true' unless ENV['NATS_PEDANTIC'].nil?
|
105
|
+
opts[:debug] = ENV['NATS_DEBUG'].downcase == 'true' unless ENV['NATS_DEBUG'].nil?
|
106
|
+
opts[:reconnect] = ENV['NATS_RECONNECT'].downcase == 'true' unless ENV['NATS_RECONNECT'].nil?
|
107
|
+
opts[:fast_producer_error] = ENV['NATS_FAST_PRODUCER'].downcase == 'true' unless ENV['NATS_FAST_PRODUCER'].nil?
|
108
|
+
opts[:ssl] = ENV['NATS_SSL'].downcase == 'true' unless ENV['NATS_SSL'].nil?
|
109
|
+
opts[:max_reconnect_attempts] = ENV['NATS_MAX_RECONNECT_ATTEMPTS'].to_i unless ENV['NATS_MAX_RECONNECT_ATTEMPTS'].nil?
|
110
|
+
opts[:reconnect_time_wait] = ENV['NATS_RECONNECT_TIME_WAIT'].to_i unless ENV['NATS_RECONNECT_TIME_WAIT'].nil?
|
111
|
+
@uri = opts[:uri] = opts[:uri].is_a?(URI) ? opts[:uri] : URI.parse(opts[:uri])
|
112
|
+
@err_cb = proc { |e| raise e } unless err_cb
|
96
113
|
check_autostart(@uri) if opts[:autostart] == true
|
97
114
|
|
98
115
|
client = EM.connect(@uri.host, @uri.port, self, opts)
|
@@ -115,7 +132,7 @@ module NATS
|
|
115
132
|
# Close the default client connection and optionally call the associated block.
|
116
133
|
# @param [Block] &blk called when the connection is closed.
|
117
134
|
def stop(&blk)
|
118
|
-
client.close if (client and client.connected?)
|
135
|
+
client.close if (client and (client.connected? || client.reconnecting?))
|
119
136
|
blk.call if blk
|
120
137
|
@@tried_autostart = {}
|
121
138
|
@err_cb = nil
|
@@ -127,18 +144,36 @@ module NATS
|
|
127
144
|
client.connected?
|
128
145
|
end
|
129
146
|
|
147
|
+
# @return [Boolean] Reconnecting state
|
148
|
+
def reconnecting?
|
149
|
+
return false unless client
|
150
|
+
client.reconnecting?
|
151
|
+
end
|
152
|
+
|
130
153
|
# @return [Hash] Options
|
131
154
|
def options
|
132
155
|
return {} unless client
|
133
156
|
client.options
|
134
157
|
end
|
135
158
|
|
159
|
+
# @return [Hash] Server information
|
160
|
+
def server_info
|
161
|
+
return nil unless client
|
162
|
+
client.server_info
|
163
|
+
end
|
164
|
+
|
136
165
|
# Set the default on_error callback.
|
137
166
|
# @param [Block] &callback called when an error has been detected.
|
138
167
|
def on_error(&callback)
|
139
168
|
@err_cb, @err_cb_overridden = callback, true
|
140
169
|
end
|
141
170
|
|
171
|
+
# Set the default on_reconnect callback.
|
172
|
+
# @param [Block] &callback called when a reconnect attempt is made.
|
173
|
+
def on_reconnect(&callback)
|
174
|
+
@reconnect_cb = callback
|
175
|
+
end
|
176
|
+
|
142
177
|
# Publish a message using the default client connection.
|
143
178
|
# @see NATS#publish
|
144
179
|
def publish(*args, &blk)
|
@@ -177,6 +212,18 @@ module NATS
|
|
177
212
|
"_INBOX.%04x%04x%04x%04x%04x%06x" % v
|
178
213
|
end
|
179
214
|
|
215
|
+
# Flushes all messages and subscriptions in the default connection
|
216
|
+
# @see NATS#flush
|
217
|
+
def flush(*args, &blk)
|
218
|
+
(@client ||= connect).flush(*args, &blk)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Return bytes outstanding for the default client connection.
|
222
|
+
# @see NATS#pending_data_size
|
223
|
+
def pending_data_size(*args)
|
224
|
+
(@client ||= connect).pending_data_size(*args)
|
225
|
+
end
|
226
|
+
|
180
227
|
def wait_for_server(uri, max_wait = 5) # :nodoc:
|
181
228
|
start = Time.now
|
182
229
|
while (Time.now - start < max_wait) # Wait max_wait seconds max
|
@@ -194,6 +241,10 @@ module NATS
|
|
194
241
|
return false
|
195
242
|
end
|
196
243
|
|
244
|
+
def clear_client # :nodoc:
|
245
|
+
@client = nil
|
246
|
+
end
|
247
|
+
|
197
248
|
private
|
198
249
|
|
199
250
|
def check_autostart(uri)
|
@@ -222,7 +273,8 @@ module NATS
|
|
222
273
|
end
|
223
274
|
|
224
275
|
attr_reader :connected, :connect_cb, :err_cb, :err_cb_overridden #:nodoc:
|
225
|
-
attr_reader :closing, :reconnecting, :options #:nodoc
|
276
|
+
attr_reader :closing, :reconnecting, :options, :server_info #:nodoc
|
277
|
+
attr_reader :msgs_received, :msgs_sent, :bytes_received, :bytes_sent, :pings
|
226
278
|
|
227
279
|
alias :connected? :connected
|
228
280
|
alias :closing? :closing
|
@@ -232,11 +284,15 @@ module NATS
|
|
232
284
|
@uri = options[:uri]
|
233
285
|
@uri.user = options[:user] if options[:user]
|
234
286
|
@uri.password = options[:pass] if options[:pass]
|
287
|
+
@ssl = options[:ssl] if options[:ssl]
|
235
288
|
@options = options
|
236
289
|
@ssid, @subs = 1, {}
|
237
290
|
@err_cb = NATS.err_cb
|
238
291
|
@reconnect_timer, @needed = nil, nil
|
292
|
+
@reconnect_cb = NATS.reconnect_cb
|
239
293
|
@connected, @closing, @reconnecting = false, false, false
|
294
|
+
@msgs_received = @msgs_sent = @bytes_received = @bytes_sent = @pings = 0
|
295
|
+
@pending_size = 0
|
240
296
|
send_connect_command
|
241
297
|
end
|
242
298
|
|
@@ -248,6 +304,11 @@ module NATS
|
|
248
304
|
def publish(subject, msg=EMPTY_MSG, opt_reply=nil, &blk)
|
249
305
|
return unless subject
|
250
306
|
msg = msg.to_s
|
307
|
+
|
308
|
+
# Accounting
|
309
|
+
@msgs_sent += 1
|
310
|
+
@bytes_sent += msg.bytesize if msg
|
311
|
+
|
251
312
|
send_command("PUB #{subject} #{opt_reply} #{msg.bytesize}#{CR_LF}#{msg}#{CR_LF}")
|
252
313
|
queue_server_rt(&blk) if blk
|
253
314
|
end
|
@@ -283,6 +344,12 @@ module NATS
|
|
283
344
|
@subs.delete(sid) unless (sub[:max] && (sub[:received] < sub[:max]))
|
284
345
|
end
|
285
346
|
|
347
|
+
# Return the active subscription count.
|
348
|
+
# @return [Number]
|
349
|
+
def subscription_count
|
350
|
+
@subs.size
|
351
|
+
end
|
352
|
+
|
286
353
|
# Setup a timeout for receiving messages for the subscription.
|
287
354
|
# @param [Object] sid
|
288
355
|
# @param [Number] timeout, float in seconds
|
@@ -323,6 +390,13 @@ module NATS
|
|
323
390
|
return s
|
324
391
|
end
|
325
392
|
|
393
|
+
# Flushes all messages and subscriptions for the connection.
|
394
|
+
# All messages and subscriptions have been processed by the server
|
395
|
+
# when the optional callback is called.
|
396
|
+
def flush(&blk)
|
397
|
+
queue_server_rt(&blk) if blk
|
398
|
+
end
|
399
|
+
|
326
400
|
# Define a callback to be called when the client connection has been established.
|
327
401
|
# @param [Block] callback
|
328
402
|
def on_connect(&callback)
|
@@ -335,7 +409,7 @@ module NATS
|
|
335
409
|
@err_cb, @err_cb_overridden = callback, true
|
336
410
|
end
|
337
411
|
|
338
|
-
# Define a callback to be called when a reconnect attempt is
|
412
|
+
# Define a callback to be called when a reconnect attempt is made.
|
339
413
|
# @param [Block] &blk called when the connection is closed.
|
340
414
|
def on_reconnect(&callback)
|
341
415
|
@reconnect_cb = callback
|
@@ -344,7 +418,14 @@ module NATS
|
|
344
418
|
# Close the connection to the server.
|
345
419
|
def close
|
346
420
|
@closing = true
|
347
|
-
|
421
|
+
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
|
422
|
+
close_connection_after_writing if connected?
|
423
|
+
process_disconnect if reconnecting?
|
424
|
+
end
|
425
|
+
|
426
|
+
# Return bytes outstanding waiting to be sent to server.
|
427
|
+
def pending_data_size
|
428
|
+
get_outbound_data_size + @pending_size
|
348
429
|
end
|
349
430
|
|
350
431
|
def user_err_cb? # :nodoc:
|
@@ -357,6 +438,7 @@ module NATS
|
|
357
438
|
cs[:user] = @uri.user
|
358
439
|
cs[:pass] = @uri.password
|
359
440
|
end
|
441
|
+
cs[:ssl_required] = @ssl if @ssl
|
360
442
|
send_command("CONNECT #{cs.to_json}#{CR_LF}")
|
361
443
|
end
|
362
444
|
|
@@ -367,10 +449,22 @@ module NATS
|
|
367
449
|
end
|
368
450
|
|
369
451
|
def on_msg(subject, sid, reply, msg) #:nodoc:
|
452
|
+
|
453
|
+
# Accounting - We should account for inbound even if they are not processed.
|
454
|
+
@msgs_received += 1
|
455
|
+
@bytes_received += msg.bytesize if msg
|
456
|
+
|
370
457
|
return unless sub = @subs[sid]
|
371
458
|
|
372
459
|
# Check for auto_unsubscribe
|
373
460
|
sub[:received] += 1
|
461
|
+
if sub[:max]
|
462
|
+
# Client side support in case server did not receive unsubscribe
|
463
|
+
return unsubscribe(sid) if (sub[:received] > sub[:max])
|
464
|
+
# cleanup here if we have hit the max..
|
465
|
+
@subs.delete(sid) if (sub[:received] == sub[:max])
|
466
|
+
end
|
467
|
+
|
374
468
|
return unsubscribe(sid) if (sub[:max] && (sub[:received] > sub[:max]))
|
375
469
|
|
376
470
|
if cb = sub[:callback]
|
@@ -391,70 +485,86 @@ module NATS
|
|
391
485
|
|
392
486
|
def flush_pending #:nodoc:
|
393
487
|
return unless @pending
|
394
|
-
@pending.
|
395
|
-
@pending = nil
|
488
|
+
send_data(@pending.join)
|
489
|
+
@pending, @pending_size = nil, 0
|
396
490
|
end
|
397
491
|
|
398
492
|
def receive_data(data) #:nodoc:
|
399
493
|
@buf = @buf ? @buf << data : data
|
400
494
|
while (@buf)
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
end
|
431
|
-
@buf = nil if (@buf && @buf.empty?)
|
432
|
-
|
433
|
-
when AWAITING_MSG_PAYLOAD
|
434
|
-
return unless (@needed && @buf.bytesize >= (@needed + CR_LF_SIZE))
|
435
|
-
on_msg(@sub, @sid, @reply, @buf.slice(0, @needed))
|
436
|
-
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
|
437
|
-
@sub = @sid = @reply = @needed = nil
|
438
|
-
@parse_state = AWAITING_CONTROL_LINE
|
439
|
-
@buf = nil if (@buf && @buf.empty?)
|
495
|
+
case @parse_state
|
496
|
+
when AWAITING_CONTROL_LINE
|
497
|
+
case @buf
|
498
|
+
when MSG
|
499
|
+
@buf = $'
|
500
|
+
@sub, @sid, @reply, @needed = $1, $2.to_i, $4, $5.to_i
|
501
|
+
@parse_state = AWAITING_MSG_PAYLOAD
|
502
|
+
when OK # No-op right now
|
503
|
+
@buf = $'
|
504
|
+
when ERR
|
505
|
+
@buf = $'
|
506
|
+
err_cb.call(NATS::ServerError.new($1))
|
507
|
+
when PING
|
508
|
+
@pings += 1
|
509
|
+
@buf = $'
|
510
|
+
send_command(PONG_RESPONSE)
|
511
|
+
when PONG
|
512
|
+
@buf = $'
|
513
|
+
cb = @pongs.shift
|
514
|
+
cb.call if cb
|
515
|
+
when INFO
|
516
|
+
@buf = $'
|
517
|
+
process_info($1)
|
518
|
+
when UNKNOWN
|
519
|
+
@buf = $'
|
520
|
+
err_cb.call(NATS::ServerError.new("Unknown protocol: $1"))
|
521
|
+
else
|
522
|
+
# If we are here we do not have a complete line yet that we understand.
|
523
|
+
return
|
440
524
|
end
|
525
|
+
@buf = nil if (@buf && @buf.empty?)
|
526
|
+
|
527
|
+
when AWAITING_MSG_PAYLOAD
|
528
|
+
return unless (@needed && @buf.bytesize >= (@needed + CR_LF_SIZE))
|
529
|
+
on_msg(@sub, @sid, @reply, @buf.slice(0, @needed))
|
530
|
+
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
|
531
|
+
@sub = @sid = @reply = @needed = nil
|
532
|
+
@parse_state = AWAITING_CONTROL_LINE
|
533
|
+
@buf = nil if (@buf && @buf.empty?)
|
534
|
+
end
|
535
|
+
|
441
536
|
end
|
442
537
|
end
|
443
538
|
|
444
539
|
def process_info(info) #:nodoc:
|
445
540
|
@server_info = JSON.parse(info, :symbolize_keys => true, :symbolize_names => true)
|
541
|
+
if @server_info[:ssl_required] && @ssl
|
542
|
+
start_tls
|
543
|
+
else
|
544
|
+
if @server_info[:ssl_required]
|
545
|
+
err_cb.call(NATS::ClientError.new('TLS/SSL required by server'))
|
546
|
+
elsif @ssl
|
547
|
+
err_cb.call(NATS::ClientError.new('TLS/SSL not supported by server'))
|
548
|
+
end
|
549
|
+
end
|
550
|
+
@server_info
|
446
551
|
end
|
447
552
|
|
448
|
-
def
|
553
|
+
def ssl_handshake_completed
|
449
554
|
@connected = true
|
555
|
+
flush_pending
|
556
|
+
end
|
557
|
+
|
558
|
+
def connection_completed #:nodoc:
|
559
|
+
@connected = true unless @ssl
|
450
560
|
if reconnecting?
|
451
561
|
EM.cancel_timer(@reconnect_timer)
|
452
562
|
send_connect_command
|
453
563
|
@subs.each_pair { |k, v| send_command("SUB #{v[:subject]} #{k}#{CR_LF}") }
|
454
564
|
end
|
455
|
-
flush_pending
|
565
|
+
flush_pending unless @ssl
|
456
566
|
unless user_err_cb? or reconnecting?
|
457
|
-
@err_cb = proc {|e| raise e }
|
567
|
+
@err_cb = proc { |e| raise e }
|
458
568
|
end
|
459
569
|
if (connect_cb and not reconnecting?)
|
460
570
|
# We will round trip the server here to make sure all state from any pending commands
|
@@ -468,43 +578,49 @@ module NATS
|
|
468
578
|
def schedule_reconnect(wait=RECONNECT_TIME_WAIT) #:nodoc:
|
469
579
|
@reconnecting = true
|
470
580
|
@reconnect_attempts = 0
|
581
|
+
@connected = false
|
471
582
|
@reconnect_timer = EM.add_periodic_timer(wait) { attempt_reconnect }
|
472
583
|
end
|
473
584
|
|
474
585
|
def unbind #:nodoc:
|
475
586
|
if connected? and not closing? and not reconnecting? and @options[:reconnect]
|
476
|
-
schedule_reconnect
|
587
|
+
schedule_reconnect(@options[:reconnect_time_wait])
|
477
588
|
else
|
478
589
|
process_disconnect unless reconnecting?
|
479
590
|
end
|
480
591
|
end
|
481
592
|
|
593
|
+
def disconnect_error_string
|
594
|
+
return "Client disconnected from server on #{@uri}." if @connected
|
595
|
+
return "Could not connect to server on #{@uri}"
|
596
|
+
end
|
597
|
+
|
482
598
|
def process_disconnect #:nodoc:
|
483
|
-
if not closing? and @err_cb
|
484
|
-
err_string = @connected ? "Client disconnected from server on #{@uri}." : "Could not connect to server on #{@uri}"
|
485
|
-
err_cb.call(NATS::ConnectError.new(err_string))
|
486
|
-
end
|
599
|
+
err_cb.call(NATS::ConnectError.new(disconnect_error_string)) if not closing? and @err_cb
|
487
600
|
ensure
|
488
601
|
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
|
489
|
-
if (NATS.client == self
|
490
|
-
|
602
|
+
if (NATS.client == self)
|
603
|
+
NATS.clear_client
|
604
|
+
EM.stop if ((connected? || reconnecting?) and closing? and not NATS.reactor_was_running?)
|
491
605
|
end
|
492
606
|
@connected = @reconnecting = false
|
493
607
|
true # Chaining
|
494
608
|
end
|
495
609
|
|
496
610
|
def attempt_reconnect #:nodoc:
|
497
|
-
process_disconnect and return if (@reconnect_attempts += 1) >
|
611
|
+
process_disconnect and return if (@reconnect_attempts += 1) > @options[:max_reconnect_attempts]
|
498
612
|
EM.reconnect(@uri.host, @uri.port, self)
|
613
|
+
@reconnect_cb.call unless @reconnect_cb.nil?
|
499
614
|
end
|
500
615
|
|
501
616
|
def send_command(command) #:nodoc:
|
502
|
-
|
503
|
-
send_data(command)
|
504
|
-
end
|
505
|
-
|
506
|
-
def queue_command(command) #:nodoc:
|
617
|
+
EM.next_tick { flush_pending } if (connected? && @pending.nil?)
|
507
618
|
(@pending ||= []) << command
|
619
|
+
@pending_size += command.bytesize
|
620
|
+
flush_pending if (connected? && @pending_size > MAX_PENDING_SIZE)
|
621
|
+
if (@options[:fast_producer_error] && pending_data_size > FAST_PRODUCER_THRESHOLD)
|
622
|
+
err_cb.call(NATS::ClientError.new("Fast Producer: #{pending_data_size} bytes outstanding"))
|
623
|
+
end
|
508
624
|
true
|
509
625
|
end
|
510
626
|
|