nats 0.3.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.
- data/COPYING +19 -0
- data/README.md +95 -0
- data/Rakefile +31 -0
- data/bin/nats-pub +20 -0
- data/bin/nats-server +5 -0
- data/bin/nats-sub +22 -0
- data/lib/nats/client.rb +424 -0
- data/lib/nats/ext/bytesize.rb +5 -0
- data/lib/nats/ext/em.rb +6 -0
- data/lib/nats/ext/json.rb +7 -0
- data/lib/nats/server.rb +313 -0
- data/lib/nats/server/const.rb +51 -0
- data/lib/nats/server/options.rb +96 -0
- data/lib/nats/server/sublist.rb +116 -0
- data/nats.gemspec +46 -0
- metadata +125 -0
data/COPYING
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Derek Collison <derek.collison@gmail.com>. All rights reserved.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
19
|
+
IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# NATS
|
2
|
+
|
3
|
+
EventMachine based Publish-Subscribe Messaging that just works.
|
4
|
+
|
5
|
+
## Supported Platforms
|
6
|
+
|
7
|
+
This gem currently works on the following Ruby platforms:
|
8
|
+
|
9
|
+
- MRI 1.8 and 1.9 (Performance is best on 1.9.2)
|
10
|
+
- Rubinius
|
11
|
+
- JRuby (not quite yet)
|
12
|
+
|
13
|
+
## Getting Started
|
14
|
+
|
15
|
+
[sudo] gem install nats
|
16
|
+
|
17
|
+
or
|
18
|
+
|
19
|
+
git clone
|
20
|
+
[sudo] rake geminstall
|
21
|
+
|
22
|
+
nats-sub foo &
|
23
|
+
nats-pub foo "Hello World!'
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
require "nats/client"
|
28
|
+
|
29
|
+
NATS.start do
|
30
|
+
|
31
|
+
# Simple Subscriber
|
32
|
+
NATS.subscribe('foo') { |msg| puts "Msg received : '#{msg}'" }
|
33
|
+
|
34
|
+
# Simple Publisher
|
35
|
+
NATS.publish('foo.bar.baz', 'Hello World!')
|
36
|
+
|
37
|
+
# Publish with closure, callback fires when server has processed the message
|
38
|
+
NATS.publish('foo', 'You done?') { puts 'msg processed!' }
|
39
|
+
|
40
|
+
# Unsubscribing
|
41
|
+
s = NATS.subscribe('bar') { |msg| puts "Msg received : '#{msg}'" }
|
42
|
+
NATS.unsubscribe(s)
|
43
|
+
|
44
|
+
# Request/Response
|
45
|
+
|
46
|
+
# The helper
|
47
|
+
NATS.subscribe('help') do |msg, reply|
|
48
|
+
NATS.publish(reply, "I'll help!")
|
49
|
+
end
|
50
|
+
|
51
|
+
# Help request
|
52
|
+
NATS.request('help') { |response|
|
53
|
+
puts "Got a response: '#{response}'"
|
54
|
+
}
|
55
|
+
|
56
|
+
# Wildcard Subscriptions
|
57
|
+
|
58
|
+
# '*" matches any token
|
59
|
+
NATS.subscribe('foo.*.baz') { |msg, _, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
60
|
+
|
61
|
+
# '>" can only be last token, and matches to any depth
|
62
|
+
NATS.subscribe('foo.>') { |msg, _, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
63
|
+
|
64
|
+
|
65
|
+
# Stop using NATS.stop, exits EM loop if NATS.start started it
|
66
|
+
NATS.stop
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
See examples and benchmark for more..
|
71
|
+
|
72
|
+
## License
|
73
|
+
|
74
|
+
(The MIT License)
|
75
|
+
|
76
|
+
Copyright (c) 2010 Derek Collison
|
77
|
+
|
78
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
79
|
+
of this software and associated documentation files (the "Software"), to
|
80
|
+
deal in the Software without restriction, including without limitation the
|
81
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
82
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
83
|
+
furnished to do so, subject to the following conditions:
|
84
|
+
|
85
|
+
The above copyright notice and this permission notice shall be included in
|
86
|
+
all copies or substantial portions of the Software.
|
87
|
+
|
88
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
89
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
90
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
91
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
92
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
93
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
94
|
+
IN THE SOFTWARE.
|
95
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
desc "Run rspec"
|
3
|
+
task :spec do
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new do |t|
|
7
|
+
t.rspec_opts = %w(-fs -c)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Build the gem"
|
12
|
+
task :gem do
|
13
|
+
sh 'gem build *.gemspec'
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Install the gem"
|
17
|
+
task :geminstall do
|
18
|
+
sh 'gem build *.gemspec'
|
19
|
+
sh 'gem install *.gem'
|
20
|
+
sh 'rm *.gem'
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Synonym for spec"
|
24
|
+
task :test => :spec
|
25
|
+
desc "Synonym for spec"
|
26
|
+
task :tests => :spec
|
27
|
+
|
28
|
+
desc "Synonym for gem"
|
29
|
+
task :pkg => :gem
|
30
|
+
desc "Synonym for gem"
|
31
|
+
task :package => :gem
|
data/bin/nats-pub
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'nats/client'
|
5
|
+
|
6
|
+
def usage
|
7
|
+
puts "Usage: nats-pub <subject> <msg>"; exit
|
8
|
+
end
|
9
|
+
|
10
|
+
subject, msg = ARGV
|
11
|
+
usage unless subject
|
12
|
+
msg ||= 'Hello World'
|
13
|
+
|
14
|
+
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
|
15
|
+
|
16
|
+
NATS.start do
|
17
|
+
NATS.publish(subject, msg) { NATS.stop }
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Published [#{subject}] : '#{msg}'"
|
data/bin/nats-server
ADDED
data/bin/nats-sub
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'nats/client'
|
5
|
+
|
6
|
+
trap("TERM") { NATS.stop }
|
7
|
+
trap("INT") { NATS.stop }
|
8
|
+
|
9
|
+
def usage
|
10
|
+
puts "Usage: nats-sub <subject>"; exit
|
11
|
+
end
|
12
|
+
|
13
|
+
subject = ARGV.shift
|
14
|
+
usage unless subject
|
15
|
+
i = 0
|
16
|
+
|
17
|
+
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
|
18
|
+
|
19
|
+
NATS.start do
|
20
|
+
puts "Listening on [#{subject}]"
|
21
|
+
NATS.subscribe(subject) { |msg, _, sub| puts "\##{i+=1}: Received on [#{sub}] : '#{msg}'" }
|
22
|
+
end
|
data/lib/nats/client.rb
ADDED
@@ -0,0 +1,424 @@
|
|
1
|
+
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + '/ext/em'
|
5
|
+
require File.dirname(__FILE__) + '/ext/bytesize'
|
6
|
+
require File.dirname(__FILE__) + '/ext/json'
|
7
|
+
|
8
|
+
# NATS is a simple publish-subscribe messaging system.
|
9
|
+
#
|
10
|
+
# == Usage
|
11
|
+
# <tt>
|
12
|
+
# require "nats/client"
|
13
|
+
#
|
14
|
+
# NATS.start do
|
15
|
+
#
|
16
|
+
# # Simple Subscriber
|
17
|
+
# NATS.subscribe('foo') { |msg| puts "Msg received : '#{msg}'" }
|
18
|
+
#
|
19
|
+
# # Simple Publisher
|
20
|
+
# NATS.publish('foo.bar.baz', 'Hello World!')
|
21
|
+
#
|
22
|
+
# # Publish with closure, callback fires when server has processed the message
|
23
|
+
# NATS.publish('foo', 'You done?') { puts 'msg processed!' }
|
24
|
+
#
|
25
|
+
# # Unsubscribing
|
26
|
+
# s = NATS.subscribe('bar') { |msg| puts "Msg received : '#{msg}'" }
|
27
|
+
# NATS.unsubscribe(s)
|
28
|
+
#
|
29
|
+
# # Request/Response
|
30
|
+
#
|
31
|
+
# # The helper
|
32
|
+
# NATS.subscribe('help') do |msg, reply|
|
33
|
+
# NATS.publish(reply, "I'll help!")
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# # Help request
|
37
|
+
# NATS.request('help') { |response|
|
38
|
+
# puts "Got a response: '#{response}'"
|
39
|
+
# }
|
40
|
+
#
|
41
|
+
# # Wildcard Subscriptions
|
42
|
+
#
|
43
|
+
# # '*" matches any token
|
44
|
+
# NATS.subscribe('foo.*.baz') { |msg, _, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
45
|
+
#
|
46
|
+
# # '>" can only be last token, and matches to any depth
|
47
|
+
# NATS.subscribe('foo.>') { |msg, _, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
48
|
+
#
|
49
|
+
#
|
50
|
+
# # Stop using NATS.stop, exits EM loop if NATS.start started it
|
51
|
+
# NATS.stop
|
52
|
+
#
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# </tt>
|
56
|
+
|
57
|
+
|
58
|
+
module NATS
|
59
|
+
|
60
|
+
# Version <b>0.3.12</b>
|
61
|
+
VERSION = "0.3.12".freeze
|
62
|
+
|
63
|
+
# Default port: <b>4222</b>
|
64
|
+
DEFAULT_PORT = 4222
|
65
|
+
|
66
|
+
# Default URI to connect to the server, <b>nats://localhost:4222</b>
|
67
|
+
DEFAULT_URI = "nats://localhost:#{DEFAULT_PORT}".freeze
|
68
|
+
|
69
|
+
# Max attempts at a reconnect: <b>10</b>
|
70
|
+
MAX_RECONNECT_ATTEMPTS = 10
|
71
|
+
|
72
|
+
# Maximum time to wait for a reconnect: <b>2 seconds</b>
|
73
|
+
RECONNECT_TIME_WAIT = 2
|
74
|
+
|
75
|
+
# Protocol
|
76
|
+
MSG = /^MSG\s+(\S+)\s+(\S+)\s+((\S+)\s+)?(\d+)$/i #:nodoc:
|
77
|
+
OK = /^\+OK/i #:nodoc:
|
78
|
+
ERR = /^-ERR\s+('.+')?/i #:nodoc:
|
79
|
+
PING = /^PING/i #:nodoc:
|
80
|
+
PONG = /^PONG/i #:nodoc:
|
81
|
+
INFO = /^INFO\s+(.+)/i #:nodoc:
|
82
|
+
|
83
|
+
# Responses
|
84
|
+
CR_LF = ("\r\n".freeze) #:nodoc:
|
85
|
+
CR_LF_SIZE = (CR_LF.bytesize) #:nodoc:
|
86
|
+
|
87
|
+
PING_REQUEST = ("PING#{CR_LF}".freeze) #:nodoc:
|
88
|
+
PONG_RESPONSE = ("PONG#{CR_LF}".freeze) #:nodoc:
|
89
|
+
|
90
|
+
EMPTY_MSG = (''.freeze) #:nodoc:
|
91
|
+
|
92
|
+
# Used for future pedantic Mode
|
93
|
+
SUB = /^([^\.\*>\s]+|>$|\*)(\.([^\.\*>\s]+|>$|\*))*$/ #:nodoc:
|
94
|
+
SUB_NO_WC = /^([^\.\*>\s]+)(\.([^\.\*>\s]+))*$/ #:nodoc:
|
95
|
+
|
96
|
+
# Duplicate autostart protection
|
97
|
+
@@tried_autostart = {}
|
98
|
+
|
99
|
+
class Error < StandardError #:nodoc:
|
100
|
+
end
|
101
|
+
|
102
|
+
class << self
|
103
|
+
attr_reader :client, :reactor_was_running, :err_cb, :err_cb_overridden #:nodoc:
|
104
|
+
alias :reactor_was_running? :reactor_was_running
|
105
|
+
|
106
|
+
# Create and return a connection to the server with the given options. The server will be autostarted if needed if
|
107
|
+
# the <b>uri</b> is determined to be local. The optional block will be called when the connection has been completed.
|
108
|
+
#
|
109
|
+
def connect(options = {}, &blk)
|
110
|
+
options[:uri] ||= ENV['NATS_URI'] || DEFAULT_URI
|
111
|
+
options[:debug] ||= ENV['NATS_DEBUG']
|
112
|
+
options[:autostart] = (ENV['NATS_AUTO'] || true) unless options[:autostart] != nil
|
113
|
+
uri = options[:uri] = URI.parse(options[:uri])
|
114
|
+
@err_cb = proc { raise Error, "Could not connect to server on #{uri}."} unless err_cb
|
115
|
+
check_autostart(uri) if options[:autostart]
|
116
|
+
client = EM.connect(uri.host, uri.port, self, options)
|
117
|
+
client.on_connect(&blk) if blk
|
118
|
+
return client
|
119
|
+
end
|
120
|
+
|
121
|
+
# Create a default client connection to the server. See connect for more information.
|
122
|
+
def start(*args, &blk)
|
123
|
+
@reactor_was_running = EM.reactor_running?
|
124
|
+
unless (@reactor_was_running || blk)
|
125
|
+
raise(Error, "EM needs to be running when NATS.start called without a run block")
|
126
|
+
end
|
127
|
+
EM.run { @client = connect(*args, &blk) }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Close the default client connection and optionally call the associated block.
|
131
|
+
def stop(&blk)
|
132
|
+
client.close if (client and client.connected?)
|
133
|
+
blk.call if blk
|
134
|
+
end
|
135
|
+
|
136
|
+
# Set the default on_error callback.
|
137
|
+
def on_error(&callback)
|
138
|
+
@err_cb, @err_cb_overridden = callback, true
|
139
|
+
end
|
140
|
+
|
141
|
+
# Publish a message using the default client connection. See NATS#publish for more information.
|
142
|
+
def publish(*args, &blk)
|
143
|
+
(@client ||= connect).publish(*args, &blk)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Subscribe using the default client connection. See NATS#subscribe for more information.
|
147
|
+
def subscribe(*args, &blk)
|
148
|
+
(@client ||= connect).subscribe(*args, &blk)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Cancel a subscription on the default client connection.
|
152
|
+
def unsubscribe(*args)
|
153
|
+
(@client ||= connect).unsubscribe(*args)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Publish a message and wait for a response on the default client connection. See NATS#request for more information.
|
157
|
+
def request(*args, &blk)
|
158
|
+
(@client ||= connect).request(*args, &blk)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns a subject that can be used for "directed" communications, utilized in #request.
|
162
|
+
def create_inbox
|
163
|
+
v = [rand(0x0010000),rand(0x0010000),rand(0x0010000),
|
164
|
+
rand(0x0010000),rand(0x0010000),rand(0x1000000)]
|
165
|
+
"_INBOX.%04x%04x%04x%04x%04x%06x" % v
|
166
|
+
end
|
167
|
+
|
168
|
+
def check_autostart(uri) #:nodoc:
|
169
|
+
return if uri_is_remote?(uri) || @@tried_autostart[uri]
|
170
|
+
@@tried_autostart[uri] = true
|
171
|
+
return if server_running?(uri)
|
172
|
+
return unless try_autostart_succeeded?(uri)
|
173
|
+
wait_for_server(uri)
|
174
|
+
end
|
175
|
+
|
176
|
+
def uri_is_remote?(uri) #:nodoc:
|
177
|
+
uri.host != 'localhost' && uri.host != '127.0.0.1'
|
178
|
+
end
|
179
|
+
|
180
|
+
def try_autostart_succeeded?(uri) #:nodoc:
|
181
|
+
port_arg = "-p #{uri.port}"
|
182
|
+
user_arg = "--user #{uri.user}" if uri.user
|
183
|
+
pass_arg = "--pass #{uri.password}" if uri.password
|
184
|
+
log_arg = '-l /tmp/nats-server.log'
|
185
|
+
pid_arg = '-P /tmp/nats-server.pid'
|
186
|
+
# daemon mode to release client
|
187
|
+
system("nats-server #{port_arg} #{user_arg} #{pass_arg} #{log_arg} #{pid_arg} -d 2> /dev/null")
|
188
|
+
$? == 0
|
189
|
+
end
|
190
|
+
|
191
|
+
def wait_for_server(uri) #:nodoc:
|
192
|
+
start = Time.now
|
193
|
+
while (Time.now - start < 5) # Wait 5 seconds max
|
194
|
+
break if server_running?(uri)
|
195
|
+
sleep(0.1)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def server_running?(uri) #:nodoc:
|
200
|
+
require 'socket'
|
201
|
+
s = TCPSocket.new(uri.host, uri.port)
|
202
|
+
s.close
|
203
|
+
return true
|
204
|
+
rescue
|
205
|
+
return false
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
attr_reader :connect_cb, :err_cb, :err_cb_overridden, :connected, :closing, :reconnecting #:nodoc:
|
211
|
+
|
212
|
+
alias :connected? :connected
|
213
|
+
alias :closing? :closing
|
214
|
+
alias :reconnecting? :reconnecting
|
215
|
+
|
216
|
+
def initialize(options)
|
217
|
+
@uri = options[:uri]
|
218
|
+
@debug = options[:debug]
|
219
|
+
@ssid, @subs = 1, {}
|
220
|
+
@err_cb = NATS.err_cb
|
221
|
+
@reconnect_timer, @needed = nil, nil
|
222
|
+
@connected, @closing, @reconnecting = false, false, false
|
223
|
+
send_connect_command
|
224
|
+
end
|
225
|
+
|
226
|
+
# Publish a message to a given subject, with optional reply subject and completion block
|
227
|
+
def publish(subject, data=EMPTY_MSG, opt_reply=nil, &blk)
|
228
|
+
return unless subject
|
229
|
+
data = data.to_s
|
230
|
+
send_command("PUB #{subject} #{opt_reply} #{data.bytesize}#{CR_LF}#{data}#{CR_LF}")
|
231
|
+
queue_server_rt(&blk) if blk
|
232
|
+
end
|
233
|
+
|
234
|
+
# Subscribe to a subject with optional wildcards. Messages will be delivered to the supplied callback.
|
235
|
+
# Callback can take any number of the supplied arguments as defined by the list: msg, reply, sub.
|
236
|
+
# Returns subscription id which can be passed to NATS#unsubscribe.
|
237
|
+
def subscribe(subject, &callback)
|
238
|
+
return unless subject
|
239
|
+
@ssid += 1
|
240
|
+
@subs[@ssid] = { :subject => subject, :callback => callback }
|
241
|
+
send_command("SUB #{subject} #{@ssid}#{CR_LF}")
|
242
|
+
@ssid
|
243
|
+
end
|
244
|
+
|
245
|
+
# Cancel a subscription.
|
246
|
+
def unsubscribe(sid)
|
247
|
+
@subs.delete(sid)
|
248
|
+
send_command("UNSUB #{sid}#{CR_LF}")
|
249
|
+
end
|
250
|
+
|
251
|
+
# Send a request and have the response delivered to the supplied callback.
|
252
|
+
# Returns subscription id which can be passed to NATS#unsubscribe.
|
253
|
+
def request(subject, data=nil, &cb)
|
254
|
+
return unless subject
|
255
|
+
inbox = NATS.create_inbox
|
256
|
+
s = subscribe(inbox) { |msg, reply|
|
257
|
+
case cb.arity
|
258
|
+
when 0 then cb.call
|
259
|
+
when 1 then cb.call(msg)
|
260
|
+
else cb.call(msg, reply)
|
261
|
+
end
|
262
|
+
}
|
263
|
+
publish(subject, data, inbox)
|
264
|
+
return s
|
265
|
+
end
|
266
|
+
|
267
|
+
# Define a callback to be called when the client connection has been established.
|
268
|
+
def on_connect(&callback)
|
269
|
+
@connect_cb = callback
|
270
|
+
end
|
271
|
+
|
272
|
+
# Define a callback to be called when errors occur on the client connection.
|
273
|
+
def on_error(&callback)
|
274
|
+
@err_cb, @err_cb_overridden = callback, true
|
275
|
+
end
|
276
|
+
|
277
|
+
# Define a callback to be called when a reconnect attempt is being made.
|
278
|
+
def on_reconnect(&callback)
|
279
|
+
@reconnect_cb = callback
|
280
|
+
end
|
281
|
+
|
282
|
+
# Close the connection to the server.
|
283
|
+
def close
|
284
|
+
@closing = true
|
285
|
+
close_connection_after_writing
|
286
|
+
end
|
287
|
+
|
288
|
+
def user_err_cb? #:nodoc:
|
289
|
+
err_cb_overridden || NATS.err_cb_overridden
|
290
|
+
end
|
291
|
+
|
292
|
+
def send_connect_command #:nodoc:
|
293
|
+
cs = { :verbose => false, :pedantic => false }
|
294
|
+
if @uri.user
|
295
|
+
cs[:user] = @uri.user
|
296
|
+
cs[:pass] = @uri.password
|
297
|
+
end
|
298
|
+
send_command("CONNECT #{cs.to_json}#{CR_LF}")
|
299
|
+
end
|
300
|
+
|
301
|
+
def queue_server_rt(&cb) #:nodoc:
|
302
|
+
return unless cb
|
303
|
+
(@pongs ||= []) << cb
|
304
|
+
send_command(PING_REQUEST)
|
305
|
+
end
|
306
|
+
|
307
|
+
def on_msg(subject, sid, reply, msg) #:nodoc:
|
308
|
+
return unless subscriber = @subs[sid]
|
309
|
+
if cb = subscriber[:callback]
|
310
|
+
case cb.arity
|
311
|
+
when 0 then cb.call
|
312
|
+
when 1 then cb.call(msg)
|
313
|
+
when 2 then cb.call(msg, reply)
|
314
|
+
else cb.call(msg, reply, subject)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def flush_pending #:nodoc:
|
320
|
+
return unless @pending
|
321
|
+
@pending.each { |p| send_data(p) }
|
322
|
+
@pending = nil
|
323
|
+
end
|
324
|
+
|
325
|
+
def receive_data(data) #:nodoc:
|
326
|
+
(@buf ||= '') << data
|
327
|
+
while (@buf && !@buf.empty?)
|
328
|
+
if (@needed && @buf.bytesize >= @needed + CR_LF_SIZE)
|
329
|
+
payload = @buf.slice(0, @needed)
|
330
|
+
on_msg(@sub, @sid, @reply, payload)
|
331
|
+
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
|
332
|
+
@sub = @sid = @reply = @needed = nil
|
333
|
+
elsif @buf =~ /^(.*)\r\n/ # Process a control line
|
334
|
+
@buf = $'
|
335
|
+
op = $1
|
336
|
+
case op
|
337
|
+
when MSG
|
338
|
+
@sub, @sid, @reply, @needed = $1, $2.to_i, $4, $5.to_i
|
339
|
+
when OK # No-op right now
|
340
|
+
when ERR
|
341
|
+
@err_cb = proc { raise Error, "Error received from server :#{$1}."} unless user_err_cb?
|
342
|
+
err_cb.call($1)
|
343
|
+
when PING
|
344
|
+
send_command(PONG_RESPONSE)
|
345
|
+
when PONG
|
346
|
+
cb = @pongs.shift
|
347
|
+
cb.call if cb
|
348
|
+
when INFO
|
349
|
+
process_info($1)
|
350
|
+
end
|
351
|
+
else # Waiting for additional data
|
352
|
+
return
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def process_info(info) #:nodoc:
|
358
|
+
@server_info = JSON.parse(info, :symbolize_keys => true)
|
359
|
+
end
|
360
|
+
|
361
|
+
def connection_completed #:nodoc:
|
362
|
+
@connected = true
|
363
|
+
if reconnecting?
|
364
|
+
EM.cancel_timer(@reconnect_timer)
|
365
|
+
send_connect_command
|
366
|
+
@subs.each_pair { |k, v| send_command("SUB #{v[:subject]} #{k}#{CR_LF}") }
|
367
|
+
end
|
368
|
+
flush_pending if @pending
|
369
|
+
@err_cb = proc { raise Error, "Client disconnected from server on #{@uri}."} unless user_err_cb? or reconnecting?
|
370
|
+
if (connect_cb and not reconnecting?)
|
371
|
+
# We will round trip the server here to make sure all state from any pending commands
|
372
|
+
# has been processed before calling the connect callback.
|
373
|
+
queue_server_rt { connect_cb.call(self) }
|
374
|
+
end
|
375
|
+
@reconnecting = false
|
376
|
+
end
|
377
|
+
|
378
|
+
def schedule_reconnect(wait=RECONNECT_TIME_WAIT) #:nodoc:
|
379
|
+
@reconnecting = true
|
380
|
+
@reconnect_attempts = 0
|
381
|
+
@reconnect_timer = EM.add_periodic_timer(wait) { attempt_reconnect }
|
382
|
+
end
|
383
|
+
|
384
|
+
def unbind #:nodoc:
|
385
|
+
if connected? and not closing? and not reconnecting?
|
386
|
+
schedule_reconnect
|
387
|
+
else
|
388
|
+
process_disconnect unless reconnecting?
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def process_disconnect #:nodoc:
|
393
|
+
if not closing? and @err_cb
|
394
|
+
err_string = @connected ? "Client disconnected from server on #{@uri}." : "Could not connect to server on #{@uri}"
|
395
|
+
err_cb.call(err_string)
|
396
|
+
end
|
397
|
+
ensure
|
398
|
+
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
|
399
|
+
EM.stop if (NATS.client == self and connected? and closing? and not NATS.reactor_was_running?)
|
400
|
+
@connected = @reconnecting = false
|
401
|
+
true # Chaining
|
402
|
+
end
|
403
|
+
|
404
|
+
def attempt_reconnect #:nodoc:
|
405
|
+
process_disconnect and return if (@reconnect_attempts += 1) > MAX_RECONNECT_ATTEMPTS
|
406
|
+
EM.reconnect(@uri.host, @uri.port, self)
|
407
|
+
end
|
408
|
+
|
409
|
+
def send_command(command) #:nodoc:
|
410
|
+
queue_command(command) and return unless connected?
|
411
|
+
send_data(command)
|
412
|
+
end
|
413
|
+
|
414
|
+
def queue_command(command) #:nodoc:
|
415
|
+
(@pending ||= []) << command
|
416
|
+
true
|
417
|
+
end
|
418
|
+
|
419
|
+
def inspect #:nodoc:
|
420
|
+
"<nats client v#{NATS::VERSION}>"
|
421
|
+
end
|
422
|
+
|
423
|
+
end
|
424
|
+
|