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 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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # NATS command line interface script.
3
+ # Run <tt>nats-server -h</tt> to get more usage.
4
+
5
+ require 'nats/server'
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
@@ -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
+