nats 0.3.12 → 0.4.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.
- data/COPYING +1 -1
- data/ChangeLog +11 -0
- data/README.md +53 -30
- data/Rakefile +2 -2
- data/bin/nats-queue +21 -0
- data/lib/nats/client.rb +244 -158
- data/lib/nats/ext/em.rb +7 -0
- data/lib/nats/server.rb +9 -291
- data/lib/nats/server/const.rb +32 -22
- data/lib/nats/server/options.rb +63 -31
- data/lib/nats/server/server.rb +307 -0
- data/lib/nats/server/sublist.rb +14 -14
- data/lib/nats/server/util.rb +34 -0
- data/nats.gemspec +12 -8
- metadata +13 -28
data/COPYING
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2010 Derek Collison <derek.collison@gmail.com>. All rights reserved.
|
1
|
+
Copyright (c) 2010, 2011 Derek Collison <derek.collison@gmail.com>. All rights reserved.
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
of this software and associated documentation files (the "Software"), to
|
data/ChangeLog
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
2011-02-21, version 0.4.2
|
2
|
+
* queue group support
|
3
|
+
* auto-unsubscribe support
|
4
|
+
* time expiration on subscriptions
|
5
|
+
* jruby initial support (1.5.6, 1.6.0-RC1)
|
6
|
+
* performance enhancements
|
7
|
+
* complete config file support
|
8
|
+
|
9
|
+
2010-11-20, version 0.3.12
|
10
|
+
* original upload to RubyGems
|
11
|
+
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# NATS
|
2
2
|
|
3
|
-
EventMachine based
|
3
|
+
A lightweight EventMachine based publish-subscribe messaging system.
|
4
4
|
|
5
5
|
## Supported Platforms
|
6
6
|
|
@@ -8,15 +8,12 @@ This gem currently works on the following Ruby platforms:
|
|
8
8
|
|
9
9
|
- MRI 1.8 and 1.9 (Performance is best on 1.9.2)
|
10
10
|
- Rubinius
|
11
|
-
- JRuby
|
11
|
+
- JRuby
|
12
12
|
|
13
13
|
## Getting Started
|
14
14
|
|
15
15
|
[sudo] gem install nats
|
16
|
-
|
17
|
-
or
|
18
|
-
|
19
|
-
git clone
|
16
|
+
== or ==
|
20
17
|
[sudo] rake geminstall
|
21
18
|
|
22
19
|
nats-sub foo &
|
@@ -34,46 +31,72 @@ This gem currently works on the following Ruby platforms:
|
|
34
31
|
# Simple Publisher
|
35
32
|
NATS.publish('foo.bar.baz', 'Hello World!')
|
36
33
|
|
37
|
-
# Publish with closure, callback fires when server has processed the message
|
38
|
-
NATS.publish('foo', 'You done?') { puts 'msg processed!' }
|
39
|
-
|
40
34
|
# Unsubscribing
|
41
|
-
|
42
|
-
NATS.unsubscribe(
|
35
|
+
sid = NATS.subscribe('bar') { |msg| puts "Msg received : '#{msg}'" }
|
36
|
+
NATS.unsubscribe(sid)
|
43
37
|
|
44
|
-
#
|
38
|
+
# Requests
|
39
|
+
NATS.request('help') { |response| puts "Got a response: '#{response}'" }
|
45
40
|
|
46
|
-
#
|
47
|
-
NATS.subscribe('help')
|
48
|
-
NATS.publish(reply, "I'll help!")
|
49
|
-
end
|
41
|
+
# Replies
|
42
|
+
NATS.subscribe('help') { |msg, reply| NATS.publish(reply, "I'll help!") }
|
50
43
|
|
51
|
-
#
|
52
|
-
NATS.
|
53
|
-
puts "Got a response: '#{response}'"
|
54
|
-
}
|
44
|
+
# Stop using NATS.stop, exits EM loop if NATS.start started the loop
|
45
|
+
NATS.stop
|
55
46
|
|
56
|
-
|
47
|
+
end
|
57
48
|
|
58
|
-
|
59
|
-
NATS.subscribe('foo.*.baz') { |msg, _, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
49
|
+
## Wildcard Subscriptions
|
60
50
|
|
61
|
-
# '
|
62
|
-
NATS.subscribe('foo
|
51
|
+
# '*" matches any token, at any level of the subject.
|
52
|
+
NATS.subscribe('foo.*.baz') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
53
|
+
NATS.subscribe('foo.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
54
|
+
NATS.subscribe('*.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
63
55
|
|
56
|
+
# '>" matches any length of the tail of a subject and can only be last token
|
57
|
+
# E.g. 'foo.>' will match 'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22'
|
58
|
+
NATS.subscribe('foo.>') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
|
64
59
|
|
65
|
-
|
66
|
-
|
60
|
+
## Queues Groups
|
61
|
+
|
62
|
+
# All subscriptions with the same queue name will form a queue group
|
63
|
+
# Each message will be delivered to only one subscriber per queue group, queuing semantics
|
64
|
+
# You can have as many queue groups as you wish
|
65
|
+
# Normal subscribers will continue to work as expected.
|
66
|
+
NATS.subscribe(subject, :queue => 'job.workers') { |msg| puts "Received '#{msg}'" }
|
67
|
+
|
68
|
+
## Advanced Usage
|
69
|
+
|
70
|
+
# Publish with closure, callback fires when server has processed the message
|
71
|
+
NATS.publish('foo', 'You done?') { puts 'msg processed!' }
|
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 }
|
79
|
+
|
80
|
+
# Auto-unsubscribe after MAX_WANTED messages received
|
81
|
+
NATS.unsubscribe(sid, MAX_WANTED)
|
82
|
+
|
83
|
+
# Multiple connections
|
84
|
+
NATS.subscribe('test') do |msg|
|
85
|
+
puts "received msg"
|
86
|
+
NATS.stop
|
87
|
+
end
|
88
|
+
|
89
|
+
# Form second connection to send message on
|
90
|
+
NATS.connect { NATS.publish('test', 'Hello World!') }
|
67
91
|
|
68
|
-
end
|
69
92
|
|
70
|
-
See examples and benchmark for more..
|
93
|
+
See examples and benchmark for more information..
|
71
94
|
|
72
95
|
## License
|
73
96
|
|
74
97
|
(The MIT License)
|
75
98
|
|
76
|
-
Copyright (c) 2010 Derek Collison
|
99
|
+
Copyright (c) 2010, 2011 Derek Collison
|
77
100
|
|
78
101
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
79
102
|
of this software and associated documentation files (the "Software"), to
|
data/Rakefile
CHANGED
data/bin/nats-queue
ADDED
@@ -0,0 +1,21 @@
|
|
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-queue <subject> <queue name>"; exit
|
11
|
+
end
|
12
|
+
|
13
|
+
subject, queue_group = ARGV
|
14
|
+
usage unless subject and queue_group
|
15
|
+
|
16
|
+
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
|
17
|
+
|
18
|
+
NATS.start do
|
19
|
+
puts "Listening on [#{subject}], queue group [#{queue_group}]"
|
20
|
+
NATS.subscribe(subject, queue_group) { |msg| puts "Received '#{msg}'" }
|
21
|
+
end
|
data/lib/nats/client.rb
CHANGED
@@ -1,84 +1,33 @@
|
|
1
|
-
|
2
1
|
require 'uri'
|
3
2
|
|
4
|
-
|
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>
|
3
|
+
ep = File.expand_path(File.dirname(__FILE__))
|
56
4
|
|
5
|
+
require "#{ep}/ext/em"
|
6
|
+
require "#{ep}/ext/bytesize"
|
7
|
+
require "#{ep}/ext/json"
|
57
8
|
|
58
9
|
module NATS
|
59
10
|
|
60
|
-
|
61
|
-
VERSION = "0.3.12".freeze
|
11
|
+
VERSION = "0.4.2".freeze
|
62
12
|
|
63
|
-
# Default port: <b>4222</b>
|
64
13
|
DEFAULT_PORT = 4222
|
65
|
-
|
66
|
-
# Default URI to connect to the server, <b>nats://localhost:4222</b>
|
67
14
|
DEFAULT_URI = "nats://localhost:#{DEFAULT_PORT}".freeze
|
68
15
|
|
69
|
-
# Max attempts at a reconnect: <b>10</b>
|
70
16
|
MAX_RECONNECT_ATTEMPTS = 10
|
71
|
-
|
72
|
-
# Maximum time to wait for a reconnect: <b>2 seconds</b>
|
73
17
|
RECONNECT_TIME_WAIT = 2
|
74
18
|
|
19
|
+
AUTOSTART_PID_FILE = '/tmp/nats-server.pid'
|
20
|
+
AUTOSTART_LOG_FILE = '/tmp/nats-server.log'
|
21
|
+
|
75
22
|
# Protocol
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
23
|
+
# @private
|
24
|
+
MSG = /\AMSG\s+([^\s\r\n]+)\s+([^\s\r\n]+)\s+(([^\s\r\n]+)[^\S\r\n]+)?(\d+)\r\n/i #:nodoc:
|
25
|
+
OK = /\A\+OK\s*\r\n/i #:nodoc:
|
26
|
+
ERR = /\A-ERR\s+('.+')?\r\n/i #:nodoc:
|
27
|
+
PING = /\APING\r\n/i #:nodoc:
|
28
|
+
PONG = /\APONG\r\n/i #:nodoc:
|
29
|
+
INFO = /\AINFO\s+([^\r\n]+)\r\n/i #:nodoc:
|
30
|
+
UNKNOWN = /\A(.*)\r\n/ #:nodoc:
|
82
31
|
|
83
32
|
# Responses
|
84
33
|
CR_LF = ("\r\n".freeze) #:nodoc:
|
@@ -93,6 +42,10 @@ module NATS
|
|
93
42
|
SUB = /^([^\.\*>\s]+|>$|\*)(\.([^\.\*>\s]+|>$|\*))*$/ #:nodoc:
|
94
43
|
SUB_NO_WC = /^([^\.\*>\s]+)(\.([^\.\*>\s]+))*$/ #:nodoc:
|
95
44
|
|
45
|
+
# Parser
|
46
|
+
AWAITING_CONTROL_LINE = 1 #:nodoc:
|
47
|
+
AWAITING_MSG_PAYLOAD = 2 #:nodoc:
|
48
|
+
|
96
49
|
# Duplicate autostart protection
|
97
50
|
@@tried_autostart = {}
|
98
51
|
|
@@ -100,72 +53,142 @@ module NATS
|
|
100
53
|
end
|
101
54
|
|
102
55
|
class << self
|
103
|
-
attr_reader
|
56
|
+
attr_reader :client, :reactor_was_running, :err_cb, :err_cb_overridden #:nodoc:
|
57
|
+
attr_accessor :timeout_cb #:nodoc
|
58
|
+
|
104
59
|
alias :reactor_was_running? :reactor_was_running
|
105
60
|
|
106
|
-
# Create and return a connection to the server with the given options.
|
107
|
-
# the <b>uri</b> is determined to be local.
|
61
|
+
# Create and return a connection to the server with the given options.
|
62
|
+
# The server will be autostarted if needed if the <b>uri</b> is determined to be local.
|
63
|
+
# The optional block will be called when the connection has been completed.
|
108
64
|
#
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
65
|
+
# @param [Hash] opts
|
66
|
+
# @option opts [String] :uri The URI to connect to, example nats://localhost:4222
|
67
|
+
# @option opts [Boolean] :autostart Boolean that can be used to suppress autostart functionality.
|
68
|
+
# @option opts [Boolean] :reconnect Boolean that can be used to suppress reconnect functionality.
|
69
|
+
# @option opts [Boolean] :debug Boolean that can be used to output additional debug information.
|
70
|
+
# @option opts [Boolean] :verbose Boolean that is sent to server for setting verbose protocol mode.
|
71
|
+
# @option opts [Boolean] :pedantic Boolean that is sent to server for setting pedantic mode.
|
72
|
+
# @param [Block] &blk called when the connection is completed. Connection will be passed to the block.
|
73
|
+
# @return [NATS] connection to the server.
|
74
|
+
def connect(opts={}, &blk)
|
75
|
+
# Defaults
|
76
|
+
opts[:verbose] = false if opts[:verbose].nil?
|
77
|
+
opts[:pedantic] = false if opts[:pedantic].nil?
|
78
|
+
opts[:reconnect] = true if opts[:reconnect].nil?
|
79
|
+
|
80
|
+
# Override with ENV
|
81
|
+
opts[:uri] ||= ENV['NATS_URI'] || DEFAULT_URI
|
82
|
+
opts[:verbose] = ENV['NATS_VERBOSE'] unless ENV['NATS_VERBOSE'].nil?
|
83
|
+
opts[:pedantic] = ENV['NATS_PEDANTIC'] unless ENV['NATS_PEDANTIC'].nil?
|
84
|
+
opts[:debug] = ENV['NATS_DEBUG'] if !ENV['NATS_DEBUG'].nil?
|
85
|
+
opts[:autostart] = (ENV['NATS_AUTO'] || true) if opts[:autostart].nil?
|
86
|
+
|
87
|
+
@uri = opts[:uri] = URI.parse(opts[:uri])
|
88
|
+
@err_cb = proc { raise Error, "Could not connect to server on #{@uri}."} unless err_cb
|
89
|
+
check_autostart(@uri) if opts[:autostart]
|
90
|
+
client = EM.connect(@uri.host, @uri.port, self, opts)
|
117
91
|
client.on_connect(&blk) if blk
|
118
92
|
return client
|
119
93
|
end
|
120
94
|
|
121
|
-
# Create a default client connection to the server.
|
95
|
+
# Create a default client connection to the server.
|
96
|
+
# @see NATS::connect
|
122
97
|
def start(*args, &blk)
|
123
98
|
@reactor_was_running = EM.reactor_running?
|
124
99
|
unless (@reactor_was_running || blk)
|
125
100
|
raise(Error, "EM needs to be running when NATS.start called without a run block")
|
126
101
|
end
|
102
|
+
# Setup optimized select versions
|
103
|
+
EM.epoll; EM.kqueue
|
127
104
|
EM.run { @client = connect(*args, &blk) }
|
128
105
|
end
|
129
106
|
|
130
107
|
# Close the default client connection and optionally call the associated block.
|
108
|
+
# @param [Block] &blk called when the connection is closed.
|
131
109
|
def stop(&blk)
|
132
110
|
client.close if (client and client.connected?)
|
133
111
|
blk.call if blk
|
112
|
+
@@tried_autostart = {}
|
113
|
+
@err_cb = nil
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [Boolean] Connected state
|
117
|
+
def connected?
|
118
|
+
return false unless client
|
119
|
+
client.connected?
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [Hash] Options
|
123
|
+
def options
|
124
|
+
return {} unless client
|
125
|
+
client.options
|
134
126
|
end
|
135
127
|
|
136
128
|
# Set the default on_error callback.
|
129
|
+
# @param [Block] &callback called when an error has been detected.
|
137
130
|
def on_error(&callback)
|
138
131
|
@err_cb, @err_cb_overridden = callback, true
|
139
132
|
end
|
140
133
|
|
141
|
-
# Publish a message using the default client connection.
|
134
|
+
# Publish a message using the default client connection.
|
135
|
+
# @see NATS#publish
|
142
136
|
def publish(*args, &blk)
|
143
137
|
(@client ||= connect).publish(*args, &blk)
|
144
138
|
end
|
145
139
|
|
146
|
-
# Subscribe using the default client connection.
|
140
|
+
# Subscribe using the default client connection.
|
141
|
+
# @see NATS#subscribe
|
147
142
|
def subscribe(*args, &blk)
|
148
143
|
(@client ||= connect).subscribe(*args, &blk)
|
149
144
|
end
|
150
145
|
|
151
146
|
# Cancel a subscription on the default client connection.
|
147
|
+
# @see NATS#unsubscribe
|
152
148
|
def unsubscribe(*args)
|
153
149
|
(@client ||= connect).unsubscribe(*args)
|
154
150
|
end
|
155
151
|
|
156
|
-
#
|
152
|
+
# Set a timeout for receiving messages for the subscription.
|
153
|
+
# @see NATS#timeout
|
154
|
+
def timeout(*args, &blk)
|
155
|
+
(@client ||= connect).timeout(*args, &blk)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Publish a message and wait for a response on the default client connection.
|
159
|
+
# @see NATS#request
|
157
160
|
def request(*args, &blk)
|
158
161
|
(@client ||= connect).request(*args, &blk)
|
159
162
|
end
|
160
163
|
|
161
|
-
# Returns a subject that can be used for "directed" communications
|
164
|
+
# Returns a subject that can be used for "directed" communications.
|
165
|
+
# @return [String]
|
162
166
|
def create_inbox
|
163
167
|
v = [rand(0x0010000),rand(0x0010000),rand(0x0010000),
|
164
168
|
rand(0x0010000),rand(0x0010000),rand(0x1000000)]
|
165
169
|
"_INBOX.%04x%04x%04x%04x%04x%06x" % v
|
166
170
|
end
|
167
171
|
|
168
|
-
def
|
172
|
+
def wait_for_server(uri, max_wait = 5) # :nodoc:
|
173
|
+
start = Time.now
|
174
|
+
while (Time.now - start < max_wait) # Wait max_wait seconds max
|
175
|
+
break if server_running?(uri)
|
176
|
+
sleep(0.1)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def server_running?(uri) # :nodoc:
|
181
|
+
require 'socket'
|
182
|
+
s = TCPSocket.new(uri.host, uri.port)
|
183
|
+
s.close
|
184
|
+
return true
|
185
|
+
rescue
|
186
|
+
return false
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def check_autostart(uri)
|
169
192
|
return if uri_is_remote?(uri) || @@tried_autostart[uri]
|
170
193
|
@@tried_autostart[uri] = true
|
171
194
|
return if server_running?(uri)
|
@@ -173,41 +196,25 @@ module NATS
|
|
173
196
|
wait_for_server(uri)
|
174
197
|
end
|
175
198
|
|
176
|
-
def uri_is_remote?(uri)
|
199
|
+
def uri_is_remote?(uri)
|
177
200
|
uri.host != 'localhost' && uri.host != '127.0.0.1'
|
178
201
|
end
|
179
202
|
|
180
|
-
def try_autostart_succeeded?(uri)
|
203
|
+
def try_autostart_succeeded?(uri)
|
181
204
|
port_arg = "-p #{uri.port}"
|
182
205
|
user_arg = "--user #{uri.user}" if uri.user
|
183
206
|
pass_arg = "--pass #{uri.password}" if uri.password
|
184
|
-
log_arg =
|
185
|
-
pid_arg =
|
207
|
+
log_arg = "-l #{AUTOSTART_LOG_FILE}"
|
208
|
+
pid_arg = "-P #{AUTOSTART_PID_FILE}"
|
186
209
|
# daemon mode to release client
|
187
210
|
system("nats-server #{port_arg} #{user_arg} #{pass_arg} #{log_arg} #{pid_arg} -d 2> /dev/null")
|
188
211
|
$? == 0
|
189
212
|
end
|
190
213
|
|
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
214
|
end
|
209
215
|
|
210
|
-
attr_reader :connect_cb, :err_cb, :err_cb_overridden
|
216
|
+
attr_reader :connected, :connect_cb, :err_cb, :err_cb_overridden #:nodoc:
|
217
|
+
attr_reader :closing, :reconnecting, :options #:nodoc
|
211
218
|
|
212
219
|
alias :connected? :connected
|
213
220
|
alias :closing? :closing
|
@@ -215,7 +222,9 @@ module NATS
|
|
215
222
|
|
216
223
|
def initialize(options)
|
217
224
|
@uri = options[:uri]
|
218
|
-
@
|
225
|
+
@uri.user = options[:user] if options[:user]
|
226
|
+
@uri.password = options[:pass] if options[:pass]
|
227
|
+
@options = options
|
219
228
|
@ssid, @subs = 1, {}
|
220
229
|
@err_cb = NATS.err_cb
|
221
230
|
@reconnect_timer, @needed = nil, nil
|
@@ -224,36 +233,78 @@ module NATS
|
|
224
233
|
end
|
225
234
|
|
226
235
|
# Publish a message to a given subject, with optional reply subject and completion block
|
227
|
-
|
236
|
+
# @param [String] subject
|
237
|
+
# @param [Object, #to_s] msg
|
238
|
+
# @param [String] opt_reply
|
239
|
+
# @param [Block] blk, closure called when publish has been processed by the server.
|
240
|
+
def publish(subject, msg=EMPTY_MSG, opt_reply=nil, &blk)
|
228
241
|
return unless subject
|
229
|
-
|
230
|
-
send_command("PUB #{subject} #{opt_reply} #{
|
242
|
+
msg = msg.to_s
|
243
|
+
send_command("PUB #{subject} #{opt_reply} #{msg.bytesize}#{CR_LF}#{msg}#{CR_LF}")
|
231
244
|
queue_server_rt(&blk) if blk
|
232
245
|
end
|
233
246
|
|
234
|
-
# Subscribe to a subject with optional wildcards.
|
247
|
+
# Subscribe to a subject with optional wildcards.
|
248
|
+
# Messages will be delivered to the supplied callback.
|
235
249
|
# 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
|
237
|
-
|
250
|
+
# Returns subscription id which can be passed to #unsubscribe.
|
251
|
+
# @param [String] subject, optionally with wilcards.
|
252
|
+
# @param [Hash] opts, optional options hash, e.g. :queue, :max.
|
253
|
+
# @param [Block] callback, called when a message is delivered.
|
254
|
+
# @return [Object] sid, Subject Identifier
|
255
|
+
def subscribe(subject, opts={}, &callback)
|
238
256
|
return unless subject
|
239
|
-
@ssid += 1
|
240
|
-
@subs[
|
241
|
-
|
242
|
-
|
257
|
+
sid = (@ssid += 1)
|
258
|
+
sub = @subs[sid] = { :subject => subject, :callback => callback, :received => 0 }
|
259
|
+
sub[:queue] = opts[:queue] if opts[:queue]
|
260
|
+
sub[:max] = opts[:max] if opts[:max]
|
261
|
+
send_command("SUB #{subject} #{opts[:queue]} #{sid}#{CR_LF}")
|
262
|
+
# Setup server support for auto-unsubscribe
|
263
|
+
unsubscribe(sid, opts[:max]) if opts[:max]
|
264
|
+
sid
|
243
265
|
end
|
244
266
|
|
245
267
|
# Cancel a subscription.
|
246
|
-
|
247
|
-
|
248
|
-
|
268
|
+
# @param [Object] sid
|
269
|
+
# @param [Number] opt_max, optional number of responses to receive before auto-unsubscribing
|
270
|
+
def unsubscribe(sid, opt_max=nil)
|
271
|
+
opt_max_str = " #{opt_max}" unless opt_max.nil?
|
272
|
+
send_command("UNSUB #{sid}#{opt_max_str}#{CR_LF}")
|
273
|
+
return unless sub = @subs[sid]
|
274
|
+
sub[:max] = opt_max
|
275
|
+
@subs.delete(sid) unless (sub[:max] && (sub[:received] < sub[:max]))
|
276
|
+
end
|
277
|
+
|
278
|
+
# Setup a timeout for receiving messages for the subscription.
|
279
|
+
# @param [Object] sid
|
280
|
+
# @param [Number] timeout, float in seconds
|
281
|
+
# @param [Hash] opts, options, :auto_unsubscribe(true), :expected(1)
|
282
|
+
def timeout(sid, timeout, opts={}, &callback)
|
283
|
+
# Setup a timeout if requested
|
284
|
+
return unless sub = @subs[sid]
|
285
|
+
|
286
|
+
auto_unsubscribe, expected = true, 1
|
287
|
+
auto_unsubscribe = opts[:auto_unsubscribe] if opts.key?(:auto_unsubscribe)
|
288
|
+
expected = opts[:expected] if opts.key?(:expected)
|
289
|
+
|
290
|
+
EM.cancel_timer(sub[:timeout]) if sub[:timeout]
|
291
|
+
|
292
|
+
sub[:timeout] = EM.add_timer(timeout) do
|
293
|
+
unsubscribe(sid) if auto_unsubscribe
|
294
|
+
callback.call(sid) if callback
|
295
|
+
end
|
296
|
+
sub[:expected] = expected
|
249
297
|
end
|
250
298
|
|
251
299
|
# Send a request and have the response delivered to the supplied callback.
|
252
|
-
#
|
253
|
-
|
300
|
+
# @param [String] subject
|
301
|
+
# @param [Object] msg
|
302
|
+
# @param [Block] callback
|
303
|
+
# @return [Object] sid
|
304
|
+
def request(subject, data=nil, opts={}, &cb)
|
254
305
|
return unless subject
|
255
306
|
inbox = NATS.create_inbox
|
256
|
-
s = subscribe(inbox) { |msg, reply|
|
307
|
+
s = subscribe(inbox, opts) { |msg, reply|
|
257
308
|
case cb.arity
|
258
309
|
when 0 then cb.call
|
259
310
|
when 1 then cb.call(msg)
|
@@ -265,16 +316,19 @@ module NATS
|
|
265
316
|
end
|
266
317
|
|
267
318
|
# Define a callback to be called when the client connection has been established.
|
319
|
+
# @param [Block] callback
|
268
320
|
def on_connect(&callback)
|
269
321
|
@connect_cb = callback
|
270
322
|
end
|
271
323
|
|
272
324
|
# Define a callback to be called when errors occur on the client connection.
|
325
|
+
# @param [Block] &blk called when the connection is closed.
|
273
326
|
def on_error(&callback)
|
274
327
|
@err_cb, @err_cb_overridden = callback, true
|
275
328
|
end
|
276
329
|
|
277
330
|
# Define a callback to be called when a reconnect attempt is being made.
|
331
|
+
# @param [Block] &blk called when the connection is closed.
|
278
332
|
def on_reconnect(&callback)
|
279
333
|
@reconnect_cb = callback
|
280
334
|
end
|
@@ -285,12 +339,12 @@ module NATS
|
|
285
339
|
close_connection_after_writing
|
286
340
|
end
|
287
341
|
|
288
|
-
def user_err_cb?
|
342
|
+
def user_err_cb? # :nodoc:
|
289
343
|
err_cb_overridden || NATS.err_cb_overridden
|
290
344
|
end
|
291
345
|
|
292
346
|
def send_connect_command #:nodoc:
|
293
|
-
cs = { :verbose =>
|
347
|
+
cs = { :verbose => @options[:verbose], :pedantic => @options[:pedantic] }
|
294
348
|
if @uri.user
|
295
349
|
cs[:user] = @uri.user
|
296
350
|
cs[:pass] = @uri.password
|
@@ -305,8 +359,13 @@ module NATS
|
|
305
359
|
end
|
306
360
|
|
307
361
|
def on_msg(subject, sid, reply, msg) #:nodoc:
|
308
|
-
return unless
|
309
|
-
|
362
|
+
return unless sub = @subs[sid]
|
363
|
+
|
364
|
+
# Check for auto_unsubscribe
|
365
|
+
sub[:received] += 1
|
366
|
+
return unsubscribe(sid) if (sub[:max] && (sub[:received] > sub[:max]))
|
367
|
+
|
368
|
+
if cb = sub[:callback]
|
310
369
|
case cb.arity
|
311
370
|
when 0 then cb.call
|
312
371
|
when 1 then cb.call(msg)
|
@@ -314,6 +373,12 @@ module NATS
|
|
314
373
|
else cb.call(msg, reply, subject)
|
315
374
|
end
|
316
375
|
end
|
376
|
+
|
377
|
+
# Check for a timeout, and cancel if received >= expected
|
378
|
+
if (sub[:timeout] && sub[:received] >= sub[:expected])
|
379
|
+
EM.cancel_timer(sub[:timeout])
|
380
|
+
sub[:timeout] = nil
|
381
|
+
end
|
317
382
|
end
|
318
383
|
|
319
384
|
def flush_pending #:nodoc:
|
@@ -323,39 +388,55 @@ module NATS
|
|
323
388
|
end
|
324
389
|
|
325
390
|
def receive_data(data) #:nodoc:
|
326
|
-
|
327
|
-
while (@buf
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
391
|
+
@buf = @buf ? @buf << data : data
|
392
|
+
while (@buf)
|
393
|
+
case @parse_state
|
394
|
+
|
395
|
+
when AWAITING_CONTROL_LINE
|
396
|
+
case @buf
|
397
|
+
when MSG
|
398
|
+
@buf = $'
|
399
|
+
@sub, @sid, @reply, @needed = $1, $2.to_i, $4, $5.to_i
|
400
|
+
@parse_state = AWAITING_MSG_PAYLOAD
|
401
|
+
when OK # No-op right now
|
402
|
+
@buf = $'
|
403
|
+
when ERR
|
404
|
+
@buf = $'
|
405
|
+
@err_cb = proc { raise Error, "Error received from server :#{$1}."} unless user_err_cb?
|
406
|
+
err_cb.call($1)
|
407
|
+
when PING
|
408
|
+
@buf = $'
|
409
|
+
send_command(PONG_RESPONSE)
|
410
|
+
when PONG
|
411
|
+
@buf = $'
|
412
|
+
cb = @pongs.shift
|
413
|
+
cb.call if cb
|
414
|
+
when INFO
|
415
|
+
@buf = $'
|
416
|
+
process_info($1)
|
417
|
+
when UNKNOWN
|
418
|
+
@buf = $'
|
419
|
+
@err_cb = proc { raise Error, "Error: Ukknown Protocol."} unless user_err_cb?
|
420
|
+
err_cb.call($1)
|
421
|
+
else
|
422
|
+
# If we are here we do not have a complete line yet that we understand.
|
423
|
+
return
|
424
|
+
end
|
425
|
+
@buf = nil if (@buf && @buf.empty?)
|
426
|
+
|
427
|
+
when AWAITING_MSG_PAYLOAD
|
428
|
+
return unless (@needed && @buf.bytesize >= (@needed + CR_LF_SIZE))
|
429
|
+
on_msg(@sub, @sid, @reply, @buf.slice(0, @needed))
|
430
|
+
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
|
431
|
+
@sub = @sid = @reply = @needed = nil
|
432
|
+
@parse_state = AWAITING_CONTROL_LINE
|
433
|
+
@buf = nil if (@buf && @buf.empty?)
|
350
434
|
end
|
351
|
-
else # Waiting for additional data
|
352
|
-
return
|
353
|
-
end
|
354
435
|
end
|
355
436
|
end
|
356
437
|
|
357
438
|
def process_info(info) #:nodoc:
|
358
|
-
@server_info = JSON.parse(info, :symbolize_keys => true)
|
439
|
+
@server_info = JSON.parse(info, :symbolize_keys => true, :symbolize_names => true)
|
359
440
|
end
|
360
441
|
|
361
442
|
def connection_completed #:nodoc:
|
@@ -366,13 +447,16 @@ module NATS
|
|
366
447
|
@subs.each_pair { |k, v| send_command("SUB #{v[:subject]} #{k}#{CR_LF}") }
|
367
448
|
end
|
368
449
|
flush_pending if @pending
|
369
|
-
|
450
|
+
unless user_err_cb? or reconnecting?
|
451
|
+
@err_cb = proc { raise Error, "Client disconnected from server on #{@uri}."}
|
452
|
+
end
|
370
453
|
if (connect_cb and not reconnecting?)
|
371
454
|
# We will round trip the server here to make sure all state from any pending commands
|
372
455
|
# has been processed before calling the connect callback.
|
373
456
|
queue_server_rt { connect_cb.call(self) }
|
374
457
|
end
|
375
458
|
@reconnecting = false
|
459
|
+
@parse_state = AWAITING_CONTROL_LINE
|
376
460
|
end
|
377
461
|
|
378
462
|
def schedule_reconnect(wait=RECONNECT_TIME_WAIT) #:nodoc:
|
@@ -382,7 +466,7 @@ module NATS
|
|
382
466
|
end
|
383
467
|
|
384
468
|
def unbind #:nodoc:
|
385
|
-
if connected? and not closing? and not reconnecting?
|
469
|
+
if connected? and not closing? and not reconnecting? and @options[:reconnect]
|
386
470
|
schedule_reconnect
|
387
471
|
else
|
388
472
|
process_disconnect unless reconnecting?
|
@@ -396,7 +480,9 @@ module NATS
|
|
396
480
|
end
|
397
481
|
ensure
|
398
482
|
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
|
399
|
-
|
483
|
+
if (NATS.client == self and connected? and closing? and not NATS.reactor_was_running?)
|
484
|
+
EM.stop
|
485
|
+
end
|
400
486
|
@connected = @reconnecting = false
|
401
487
|
true # Chaining
|
402
488
|
end
|