Showing preview only (426K chars total). Download the full file or copy to clipboard to get everything.
Repository: nats-io/ruby-nats
Branch: main
Commit: 639d4d0dc154
Files: 132
Total size: 394.2 KB
Directory structure:
gitextract_06zlpyqi/
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── config.yml
│ ├── defect.yml
│ └── proposal.yml
├── .gitignore
├── .travis.yml
├── CODE-OF-CONDUCT.md
├── GOVERNANCE.md
├── Gemfile
├── HISTORY.md
├── LICENSE
├── MAINTAINERS.md
├── README.md
├── Rakefile
├── TODO
├── benchmark/
│ ├── latency_perf.rb
│ ├── pub_perf.rb
│ ├── pub_sub_perf.rb
│ ├── queues_perf.rb
│ ├── sub_perf.rb
│ └── sublist_perf.rb
├── bin/
│ ├── nats-pub
│ ├── nats-queue
│ ├── nats-request
│ ├── nats-server
│ ├── nats-sub
│ └── nats-top
├── dependencies.md
├── examples/
│ ├── auth_pub.rb
│ ├── auth_sub.rb
│ ├── auto_unsub.rb
│ ├── busy_body.rb
│ ├── drain_connection.rb
│ ├── expected.rb
│ ├── fiber_request.rb
│ ├── multi_connection.rb
│ ├── pub.rb
│ ├── queue_sub.rb
│ ├── request.rb
│ ├── server_config.yml
│ ├── server_config_cluster.yml
│ ├── simple.rb
│ ├── sub.rb
│ ├── sub_timeout.rb
│ ├── tls-connect.rb
│ └── tls.rb
├── lib/
│ └── nats/
│ ├── client.rb
│ ├── ext/
│ │ ├── bytesize.rb
│ │ ├── em.rb
│ │ └── json.rb
│ ├── nuid.rb
│ ├── server/
│ │ ├── cluster.rb
│ │ ├── connection.rb
│ │ ├── connz.rb
│ │ ├── const.rb
│ │ ├── options.rb
│ │ ├── route.rb
│ │ ├── server.rb
│ │ ├── sublist.rb
│ │ ├── util.rb
│ │ └── varz.rb
│ ├── server.rb
│ └── version.rb
├── nats.gemspec
├── scripts/
│ └── install_gnatsd.sh
└── spec/
├── .rspec
├── client/
│ ├── attack_spec.rb
│ ├── auth_spec.rb
│ ├── autounsub_spec.rb
│ ├── binary_msg_spec.rb
│ ├── client_cluster_config_spec.rb
│ ├── client_cluster_reconnect_spec.rb
│ ├── client_config_spec.rb
│ ├── client_connect_spec.rb
│ ├── client_drain_spec.rb
│ ├── client_nkeys_connect_spec.rb
│ ├── client_requests_spec.rb
│ ├── client_spec.rb
│ ├── client_tls_spec.rb
│ ├── cluster_auth_token_spec.rb
│ ├── cluster_auto_discovery_spec.rb
│ ├── cluster_lb_spec.rb
│ ├── cluster_multi_route_spec.rb
│ ├── cluster_retry_connect_spec.rb
│ ├── cluster_spec.rb
│ ├── error_on_client_spec.rb
│ ├── fast_producer_spec.rb
│ ├── nuid_spec.rb
│ ├── partial_message_spec.rb
│ ├── queues_spec.rb
│ ├── reconnect_spec.rb
│ ├── server_info_spec.rb
│ └── sub_timeouts_spec.rb
├── configs/
│ ├── certs/
│ │ ├── bad-ca.pem
│ │ ├── ca.pem
│ │ ├── client-cert.pem
│ │ ├── client-key.pem
│ │ ├── key.pem
│ │ ├── multi-ca.pem
│ │ └── server.pem
│ ├── nkeys/
│ │ ├── foo-user.creds
│ │ ├── foo-user.jwt
│ │ ├── foo-user.nk
│ │ └── op.jwt
│ ├── tls-no-auth.conf
│ ├── tls.conf
│ └── tlsverify.conf
├── server/
│ ├── max_connections_spec.rb
│ ├── monitor_spec.rb
│ ├── multi_user_auth_spec.rb
│ ├── protocol_spec.rb
│ ├── resources/
│ │ ├── auth.yml
│ │ ├── b1_cluster.yml
│ │ ├── b2_cluster.yml
│ │ ├── cluster.yml
│ │ ├── config.yml
│ │ ├── max_connections.yml
│ │ ├── mixed_auth.yml
│ │ ├── monitor.yml
│ │ ├── multi_user_auth.yml
│ │ ├── multi_user_auth_long.yml
│ │ ├── ping.yml
│ │ ├── s1_cluster.yml
│ │ ├── s2_cluster.yml
│ │ └── s3_cluster.yml
│ ├── server_cluster_config_spec.rb
│ ├── server_config_spec.rb
│ ├── server_exitcode_spec.rb
│ ├── server_log_spec.rb
│ ├── server_ping_spec.rb
│ ├── ssl_spec.rb
│ └── sublist_spec.rb
└── spec_helper.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Discussion
url: https://github.com/nats-io/nats.rb/discussions
about: Ideal for ideas, feedback, or longer form questions.
- name: Chat
url: https://slack.nats.io
about: Ideal for short, one-off questions, general conversation, and meeting other NATS users!
================================================
FILE: .github/ISSUE_TEMPLATE/defect.yml
================================================
---
name: Defect
description: Report a defect, such as a bug or regression.
labels:
- defect
body:
- type: textarea
id: versions
attributes:
label: What version were you using?
description: Include the server version (`nats-server --version`) and any client versions when observing the issue.
validations:
required: true
- type: textarea
id: environment
attributes:
label: What environment was the server running in?
description: This pertains to the operating system, CPU architecture, and/or Docker image that was used.
validations:
required: true
- type: textarea
id: steps
attributes:
label: Is this defect reproducible?
description: Provide best-effort steps to showcase the defect.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Given the capability you are leveraging, describe your expectation?
description: This may be the expected behavior or performance characteristics.
validations:
required: true
- type: textarea
id: actual
attributes:
label: Given the expectation, what is the defect you are observing?
description: This may be an unexpected behavior or regression in performance.
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/proposal.yml
================================================
---
name: Proposal
description: Propose an enhancement or new feature.
labels:
- proposal
body:
- type: textarea
id: usecase
attributes:
label: What motivated this proposal?
description: Describe the use case justifying this request.
validations:
required: true
- type: textarea
id: change
attributes:
label: What is the proposed change?
description: This could be a behavior change, enhanced API, or a branch new feature.
validations:
required: true
- type: textarea
id: benefits
attributes:
label: Who benefits from this change?
description: Describe how this not only benefits you.
validations:
required: false
- type: textarea
id: alternates
attributes:
label: What alternatives have you evaluated?
description: This could be using existing features or relying on an external dependency.
validations:
required: false
================================================
FILE: .gitignore
================================================
*~
\#*\#
.\#*
*.rbc
.rbx
.bundle
*.gem
.DS_Store
.idea
================================================
FILE: .travis.yml
================================================
language: ruby
rvm:
- 2.7
cache:
directories:
- $HOME/nats-server
before_install:
- bash ./scripts/install_gnatsd.sh
before_script:
- export PATH=$HOME/nats-server:$PATH
sudo: required
dist: xenial
bundler_args: --without server
================================================
FILE: CODE-OF-CONDUCT.md
================================================
## Community Code of Conduct
NATS follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
================================================
FILE: GOVERNANCE.md
================================================
# NATS Ruby Client Governance
NATS Ruby Client is part of the NATS project and is subject to the [NATS Governance](https://github.com/nats-io/nats-general/blob/master/GOVERNANCE.md).
================================================
FILE: Gemfile
================================================
source "http://rubygems.org"
gemspec
group :test do
gem 'rake'
gem 'rspec'
end
group :server do
gem 'daemons'
gem 'json_pure'
gem 'thin'
gem 'rack', ">= 2.0.6"
end
group :v2 do
gem 'nkeys'
end
================================================
FILE: HISTORY.md
================================================
# HISTORY
## v0.11.0 (June 10, 2019)
- NATS v2.0 support! (#162)
## v0.8.4 (Feb 23, 2018)
- Support to include connection `name` as part of CONNECT options (#145)
- Fixed support for Ruby 2.5 due to missing OpenSSL `require` (#144)
## v0.8.2 (March 14, 2017)
- Allow setting name from client on connect (#129)
- Add discovered servers helper for servers announced via async INFO (#136)
- Add time based reconnect backoff (#139)
- Modify lang sent on connect when using jruby (#135)
- Update eventmachine dependencies (#134)
## v0.8.0 (August 10, 2016)
- Added cluster auto discovery handling which is supported on v0.9.2 server release (#125)
- Added jruby part of the build (both in openjdk and oraclejdk runtimes) (#122 #123)
- Fixed ping interval accounting (#120)
## v0.7.1 (July 8, 2016)
- Remove dependencies which are no longer needed for ruby-client
- See full list @ https://github.com/nats-io/ruby-nats/compare/v0.7.0...v0.7.1
## v0.7.0 (July 8, 2016)
- Enhanced TLS support: certificates and verify peer functionality added
- Bumped version of Eventmachine to 1.2 series
- See full list @ https://github.com/nats-io/ruby-nats/compare/v0.6.0...v0.7.0
## v0.6.0 (March 22, 2016)
- Removed distributing `nats-server` along with the gem
- Fixed issue with subscriptions not being sent on first reconnect (#94)
- Added loading Oj gem for JSON when supported (#91)
- Fixed removing warning message introduced by EM 1.0.8 (#90)
- Changed to testing spec with `gnatsd` (#95)
- See full list @ https://github.com/nats-io/ruby-nats/compare/v0.5.1...v0.6.0
## v0.5.1 (August 7, 2015)
- Changed to never remove servers when configured as such (#88)
- See full list @ https://github.com/nats-io/ruby-nats/compare/v0.5.0...v0.5.1
## v0.5.0 (June 19, 2015)
- See full list @ https://github.com/nats-io/ruby-nats/compare/v0.5.0.beta.16...v0.5.0
## v0.5.0.beta.16 (December 7, 2014)
- Resolved major issue on cluster connects to non-first server, issue #78
- Official Support for Ruby 2.1
- See full list @ https://github.com/derekcollison/nats/compare/v0.5.0.beta.12...v0.5.0.beta.16
## v0.5.0.beta.12 (October 1, 2013)
- Fixed issue #58, reconnects not stopped on auth failures
- Fixed leaking ping timers on auth failures
- Created AuthError
- See full list @ https://github.com/derekcollison/nats/compare/v0.5.0.beta.11...v0.5.0.beta.12
## v0.5.0.beta.11 (July 26, 2013)
- Bi-directional Route designation
- Upgrade to EM 1.x
- See full list @ https://github.com/derekcollison/nats/compare/v0.5.0.beta.1...v0.5.0.beta.11
## v0.5.0.beta.1 (Sept 10, 2012)
- Clustering support for nats-servers
- Reconnect client logic cluster aware (explicit servers only for now)
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.26...v0.5.0.beta.1
## v0.4.28 (September 22, 2012)
- Binary payload bug fix
- Lock EM to version 0.12.10, 1.0 does not pass tests currently.
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.26...v0.4.28
## v0.4.26 (July 30, 2012)
- Syslog support
- Fixed reconnect bug to authorized servers
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.24...v0.4.26
## v0.4.24 (May 24, 2012)
- Persist queue groups across reconnects
- Proper exit codes for nats-server
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.22...v0.4.24
## v0.4.22 (Mar 5, 2012)
- HTTP based server monitoring (/varz, /connz, /healthz)
- Perfomance and Stability improvements
- Client monitoring
- Server to Client pings
- Multiple Auth users
- SSL/TSL support
- nats-top utility
- Connection state dump on SIGUSR2
- Client Server information support
- Client Fast Producer support
- Client reconenct callbacks
- Server Max Connections support
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.10...v0.4.22
## v0.4.10 (Apr 21, 2011)
- Minor bug fixes
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.8...v0.4.10
## v0.4.8 (Apr 2, 2011)
- Minor bug fixes
- See full list @ https://github.com/derekcollison/nats/compare/v0.4.2...v0.4.8
## v0.4.2 (Feb 21, 2011)
- Queue group support
- Auto-unsubscribe support
- Time expiration on subscriptions
- Jruby initial support
- Performance enhancements
- Complete config file support
- See full list @ https://github.com/derekcollison/nats/compare/v0.3.12...v0.4.2
## v0.3.12 (Nov 21, 2010)
- Initial Release
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MAINTAINERS.md
================================================
# Maintainers
Maintainership is on a per project basis.
### Maintainers
- Derek Collison <derek@nats.io> [@derekcollison](https://github.com/derekcollison)
- Waldemar Quevedo <wally@nats.io> [@wallyqs](https://github.com/wallyqs)
================================================
FILE: README.md
================================================
# NATS - Ruby Client
A [Ruby](http://ruby-lang.org) client for the [NATS messaging system](https://nats.io).
[](https://www.apache.org/licenses/LICENSE-2.0)
[](https://app.travis-ci.com/nats-io/nats.rb)
[](https://rubygems.org/gems/nats/versions/0.11.0)
[](https://www.rubydoc.info/gems/nats)
## Getting Started
```bash
gem install nats
nats-sub foo &
nats-pub foo 'Hello World!'
```
Starting from [v0.11.0](https://github.com/nats-io/nats.rb/releases/tag/v0.11.0) release,
you can also optionally install [NKEYS](https://github.com/nats-io/nkeys.rb) in order to use
the new NATS v2.0 auth features:
```bash
gem install nkeys
```
If you're looking for a non-EventMachine alternative, check out the [nats-pure](https://github.com/nats-io/nats-pure.rb) gem.
## Basic Usage
```ruby
require "nats/client"
NATS.start do
# Simple Subscriber
NATS.subscribe('foo') { |msg| puts "Msg received : '#{msg}'" }
# Simple Publisher
NATS.publish('foo.bar.baz', 'Hello World!')
# Unsubscribing
sid = NATS.subscribe('bar') { |msg| puts "Msg received : '#{msg}'" }
NATS.unsubscribe(sid)
# Requests
NATS.request('help') { |response| puts "Got a response: '#{response}'" }
# Replies
NATS.subscribe('help') { |msg, reply| NATS.publish(reply, "I'll help!") }
# Stop using NATS.stop, exits EM loop if NATS.start started the loop
NATS.stop
end
```
## Wildcard Subscriptions
```ruby
# "*" matches any token, at any level of the subject.
NATS.subscribe('foo.*.baz') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
NATS.subscribe('foo.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
NATS.subscribe('*.bar.*') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
# ">" matches any length of the tail of a subject and can only be the last token
# E.g. 'foo.>' will match 'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22'
NATS.subscribe('foo.>') { |msg, reply, sub| puts "Msg received on [#{sub}] : '#{msg}'" }
```
## Queues Groups
```ruby
# All subscriptions with the same queue name will form a queue group
# Each message will be delivered to only one subscriber per queue group, queuing semantics
# You can have as many queue groups as you wish
# Normal subscribers will continue to work as expected.
NATS.subscribe(subject, :queue => 'job.workers') { |msg| puts "Received '#{msg}'" }
```
## Clustered Usage
```ruby
NATS.start(:servers => ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223']) do |nc|
puts "NATS is connected to #{nc.connected_server}"
nc.on_reconnect do
puts "Reconnected to server at #{nc.connected_server}"
end
nc.on_disconnect do |reason|
puts "Disconnected: #{reason}"
end
nc.on_close do
puts "Connection to NATS closed"
end
end
opts = {
:dont_randomize_servers => true,
:reconnect_time_wait => 0.5,
:max_reconnect_attempts => 10,
:servers => ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223', 'nats://127.0.0.1:4224']
}
NATS.connect(opts) do |c|
puts "NATS is connected!"
end
```
### Auto discovery
The client also auto discovers new nodes announced by the server as
they attach to the cluster. Reconnection logic parameters such as
time to back-off on failure and max attempts apply the same to both
discovered nodes and those defined explicitly on connect:
```ruby
opts = {
:dont_randomize_servers => true,
:reconnect_time_wait => 0.5,
:max_reconnect_attempts => 10,
:servers => ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223'],
:user => 'secret',
:pass => 'deadbeef'
}
NATS.connect(opts) do |c|
# Confirm number of available servers in cluster.
puts "Connected to NATS! Servers in pool: #{c.server_pool.count}"
end
```
## Advanced Usage
```ruby
# Publish with closure, callback fires when server has processed the message
NATS.publish('foo', 'You done?') { puts 'msg processed!' }
# Timeouts for subscriptions
sid = NATS.subscribe('foo') { received += 1 }
NATS.timeout(sid, TIMEOUT_IN_SECS) { timeout_recvd = true }
# Timeout unless a certain number of messages have been received
NATS.timeout(sid, TIMEOUT_IN_SECS, :expected => 2) { timeout_recvd = true }
# Auto-unsubscribe after MAX_WANTED messages received
NATS.unsubscribe(sid, MAX_WANTED)
# Multiple connections
NATS.subscribe('test') do |msg|
puts "received msg"
# Gracefully disconnect from NATS after handling
# messages that have already been delivered by server.
NATS.drain
end
# Form second connection to send message on
NATS.connect { NATS.publish('test', 'Hello World!') }
```
See examples and benchmarks for more information..
### TLS
Advanced customizations options for setting up a secure connection can
be done by including them on connect:
```ruby
options = {
:servers => [
'nats://secret:deadbeef@127.0.0.1:4443',
'nats://secret:deadbeef@127.0.0.1:4444'
],
:max_reconnect_attempts => 10,
:reconnect_time_wait => 2,
:tls => {
:private_key_file => './spec/configs/certs/key.pem',
:cert_chain_file => './spec/configs/certs/server.pem'
# Can enable verify_peer functionality optionally by passing
# the location of a ca_file.
# :verify_peer => true,
# :ca_file => './spec/configs/certs/ca.pem'
}
}
# Set default callbacks
NATS.on_error do |e|
puts "Error: #{e}"
end
NATS.on_disconnect do |reason|
puts "Disconnected: #{reason}"
end
NATS.on_reconnect do |nats|
puts "Reconnected to NATS server at #{nats.connected_server}"
end
NATS.on_close do
puts "Connection to NATS closed"
EM.stop
end
NATS.start(options) do |nats|
puts "Connected to NATS at #{nats.connected_server}"
nats.subscribe("hello") do |msg|
puts "Received: #{msg}"
end
nats.flush do
nats.publish("hello", "world")
end
end
```
### Fibers
Requests without a callback can be made to work synchronously and return
the result when running in a Fiber. For these type of requests, it is
possible to set a timeout of how long to wait for a single or multiple
responses.
```ruby
NATS.start {
NATS.subscribe('help') do |msg, reply|
puts "[Received]: <<- #{msg}"
NATS.publish(reply, "I'll help! - #{msg}")
end
NATS.subscribe('slow') do |msg, reply|
puts "[Received]: <<- #{msg}"
EM.add_timer(1) { NATS.publish(reply, "I'll help! - #{msg}") }
end
10.times do |n|
NATS.subscribe('hi') do |msg, reply|
NATS.publish(reply, "Hello World! - id:#{n}")
end
end
Fiber.new do
# Requests work synchronously within the same Fiber
# returning the message when done.
response = NATS.request('help', 'foo')
puts "[Response]: ->> '#{response}'"
# Specifying a custom timeout to give up waiting for
# a response.
response = NATS.request('slow', 'bar', timeout: 2)
if response.nil?
puts "No response after 2 seconds..."
else
puts "[Response]: ->> '#{response}'"
end
# Can gather multiple responses with the same request
# which will then return a collection with the responses
# that were received before the timeout.
responses = NATS.request('hi', 'quux', max: 10, timeout: 1)
responses.each_with_index do |response, i|
puts "[Response# #{i}]: ->> '#{response}'"
end
# If no replies then an empty collection is returned.
responses = NATS.request('nowhere', '', max: 10, timeout: 2)
if responses.any?
puts "Got #{responses.count} responses"
else
puts "No response after 2 seconds..."
end
NATS.stop
end.resume
# Multiple fibers can make requests concurrently
# under the same Eventmachine loop.
Fiber.new do
10.times do |n|
response = NATS.request('help', "help.#{n}")
puts "[Response]: ->> '#{response}'"
end
end.resume
}
```
### New Authentication (Nkeys and User Credentials)
This requires server with version >= 2.0.0
NATS servers have a new security and authentication mechanism to authenticate with user credentials and NKEYS. A single file containing the JWT and NKEYS to authenticate against a NATS v2 server can be set with the `user_credentials` option:
```ruby
require 'nats/client'
NATS.start("tls://connect.ngs.global", user_credentials: "/path/to/creds") do |nc|
nc.subscribe("hello") do |msg|
puts "[Received] #{msg}"
end
nc.publish('hello', 'world')
end
```
This will create two callback handlers to present the user JWT and sign the nonce challenge from the server. The core client library never has direct access to your private key and simply performs the callback for signing the server challenge. The library will load and wipe and clear the objects it uses for each connect or reconnect.
Bare NKEYS are also supported. The nkey seed should be in a read only file, e.g. `seed.txt`.
```bash
> cat seed.txt
# This is my seed nkey!
SUAGMJH5XLGZKQQWAWKRZJIGMOU4HPFUYLXJMXOO5NLFEO2OOQJ5LPRDPM
```
Then in the client specify the path to the seed using the `nkeys_seed` option:
```ruby
require 'nats/client'
NATS.start("tls://connect.ngs.global", nkeys_seed: "path/to/seed.txt") do |nc|
nc.subscribe("hello") do |msg|
puts "[Received] #{msg}"
end
nc.publish('hello', 'world')
end
```
## License
Unless otherwise noted, the NATS source files are distributed under
the Apache Version 2.0 license found in the LICENSE file.
================================================
FILE: Rakefile
================================================
#!/usr/bin/env rake
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'rspec/core'
require 'rspec/core/rake_task'
task :default => 'spec:client'
desc 'Run specs from client and server'
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = FileList['spec/**/*_spec.rb']
spec.rspec_opts = ["--format", "documentation", "--colour", "--profile"]
end
desc 'Run spec from client using gnatsd as the server'
RSpec::Core::RakeTask.new('spec:client') do |spec|
spec.pattern = FileList['spec/client/*_spec.rb']
spec.rspec_opts = ["--format", "documentation", "--colour", "--profile"]
end
desc 'Run spec from client on jruby using gnatsd as the server'
RSpec::Core::RakeTask.new('spec:client:jruby') do |spec|
spec.pattern = FileList['spec/client/*_spec.rb']
spec.rspec_opts = ["--format", "documentation", "--colour", "--tag", "~jruby_excluded", "--profile"]
end
desc 'Run spec from server'
RSpec::Core::RakeTask.new('spec:server') do |spec|
spec.pattern = FileList['spec/server/*_spec.rb']
spec.rspec_opts = ["--format", "documentation", "--colour"]
end
desc "Build the gem"
task :gem do
sh 'gem build *.gemspec'
end
desc "Install the gem"
task :geminstall do
sh 'gem build *.gemspec'
sh 'gem install *.gem'
sh 'rm *.gem'
end
desc "Synonym for spec"
task :test => :spec
desc "Synonym for spec"
task :tests => :spec
desc "Synonym for gem"
task :pkg => :gem
desc "Synonym for gem"
task :package => :gem
================================================
FILE: TODO
================================================
- [DONE] Contributing guidelines
- [DONE] cluster tests for travis-ci
- [DONE] clustering/routing
- [DONE] Allow implicit route information to propagate to clients?
- [DONE] Check with EM 1.0 not working
- [DONE] Client ping timers
- [DONE] Retry timers on routes
- [DONE] Random on cluster client server pool
- do client blowup detection from sending in tight loop
- allow port to also be listen_port in config file
- queue groups allow selection, e.g. round robin vs random?
- Logger rotation should work
- [DONE] Allow clients to specify app/client name
- [DONE] tests!
- [DONE] JRuby support - works if you hand run servers for tests
- [DONE] Add a disconnect cb
- [DONE] balance receive perf with send perf (EM issue, iovec)
- [DONE] Time interval Pings from server..
- [DONE] Ping/Pong time window
- [DONE] monitoring
- [DONE] Allow monitoring to specify on net param
- [DONE] /connz info
- [DONE] Don't truncate log on startup
- [DONE] Connection queue size dump to log for SIGURS2
- [DONE] Fixed # of replies? sub foo [2], server closes subscription automatically, avoid overwhelming client connection?
- [DONE] Request timeout option.
- [DONE] proper flow control, EM and PING/PONG
- [DONE] request/response automated, just use UUID now.
- [DONE] better reply semantics in protocol.
- [DONE] daemon mode
- [DONE] autolaunch
- [DONE] Queue groups
================================================
FILE: benchmark/latency_perf.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
$LOAD_PATH << File.expand_path('../../lib', __FILE__)
require 'nats/client'
$loop = 10000
$hash = 1000
$sub = 'test'
STDOUT.sync = true
parser = OptionParser.new do |opts|
opts.banner = "Usage: latency_perf [options]"
opts.separator ""
opts.separator "options:"
opts.on("-n ITERATIONS", "iterations to expect (default: #{$loop})") { |iter| $loop = iter.to_i }
end
parser.parse(ARGV)
$drain = $loop
trap("TERM") { exit! }
trap("INT") { exit! }
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start do
def done
ms = "%.2f" % (((Time.now-$start)/$loop)*1000.0)
puts "\nTest completed : #{ms} ms avg request/response latency\n"
NATS.stop
end
def send_request
s = NATS.request('test') {
$drain-=1
if $drain == 0
done
else
send_request
printf('+') if $drain.modulo($hash) == 0
end
NATS.unsubscribe(s)
}
end
s_conn = NATS.connect
s_conn.subscribe('test') do |msg, reply|
s_conn.publish(reply)
end
# Send first request when we are connected with subscriber
s_conn.on_connect {
puts "Sending #{$loop} request/responses"
$start = Time.now
send_request
}
end
================================================
FILE: benchmark/pub_perf.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
$:.unshift File.expand_path('../../lib', __FILE__)
require 'nats/client'
$count = 100000
$batch = 100
$delay = 0.00001
$dmin = 0.00001
TRIP = (2*1024*1024)
TSIZE = 4*1024
$sub = 'test'
$data_size = 16
$hash = 2500
STDOUT.sync = true
parser = OptionParser.new do |opts|
opts.banner = "Usage: pub_perf [options]"
opts.separator ""
opts.separator "options:"
opts.on("-n COUNT", "Messages to send (default: #{$count}}") { |count| $count = count.to_i }
opts.on("-s SIZE", "Message size (default: #{$data_size})") { |size| $data_size = size.to_i }
opts.on("-S SUBJECT", "Send subject (default: (#{$sub})") { |sub| $sub = sub }
opts.on("-b BATCH", "Batch size (default: (#{$batch})") { |batch| $batch = batch.to_i }
end
parser.parse(ARGV)
trap("TERM") { exit! }
trap("INT") { exit! }
NATS.on_error { |err| puts "Error: #{err}"; exit! }
$data = Array.new($data_size) { "%01x" % rand(16) }.join('').freeze
NATS.start(:fast_producer_error => true) do
$start = Time.now
$to_send = $count
$batch = 10 if $data_size >= TSIZE
def send_batch
(0..$batch).each do
$to_send -= 1
if $to_send == 0
NATS.publish($sub, $data) { display_final_results }
return
else
NATS.publish($sub, $data)
end
printf('+') if $to_send.modulo($hash) == 0
end
if (NATS.pending_data_size > TRIP)
$delay *= 2
elsif $delay > $dmin
$delay /= 2
end
EM.add_timer($delay) { send_batch }
end
def display_final_results
elapsed = Time.now - $start
mbytes = sprintf("%.1f", (($data_size*$count)/elapsed)/(1024*1024))
puts "\nTest completed : #{($count/elapsed).ceil} msgs/sec (#{mbytes} MB/sec)\n"
NATS.stop
end
if false
EM.add_periodic_timer(0.25) do
puts "Outstanding data size is #{NATS.client.get_outbound_data_size}"
end
end
puts "Sending #{$count} messages of size #{$data.size} bytes on [#{$sub}]"
# kick things off..
send_batch
end
================================================
FILE: benchmark/pub_sub_perf.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
$:.unshift File.expand_path('../../lib', __FILE__)
require 'nats/client'
$count = 100000
$batch = 100
$delay = 0.00001
$dmin = 0.00001
TRIP = (2*1024*1024)
TSIZE = 4*1024
$sub = 'test'
$data_size = 16
$hash = 2500
STDOUT.sync = true
parser = OptionParser.new do |opts|
opts.banner = "Usage: pub_perf [options]"
opts.separator ""
opts.separator "options:"
opts.on("-n COUNT", "Messages to send (default: #{$count}}") { |count| $count = count.to_i }
opts.on("-s SIZE", "Message size (default: #{$data_size})") { |size| $data_size = size.to_i }
opts.on("-S SUBJECT", "Send subject (default: (#{$sub})") { |sub| $sub = sub }
opts.on("-b BATCH", "Batch size (default: (#{$batch})") { |batch| $batch = batch.to_i }
end
parser.parse(ARGV)
trap("TERM") { exit! }
trap("INT") { exit! }
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
$data = Array.new($data_size) { "%01x" % rand(16) }.join('').freeze
NATS.start(:fast_producer => true) do
$batch = 10 if $data_size >= TSIZE
$received = 0
NATS.subscribe($sub) { $received += 1 }
$start = Time.now
$to_send = $count
def send_batch
(0..$batch).each do
$to_send -= 1
if $to_send == 0
NATS.publish($sub, $data) { display_final_results }
return
else
NATS.publish($sub, $data)
end
printf('+') if $to_send.modulo($hash) == 0
end
if (NATS.pending_data_size > TRIP)
$delay *= 2
elsif $delay > $dmin
$delay /= 2
end
EM.add_timer($delay) { send_batch }
end
def display_final_results
elapsed = Time.now - $start
mbytes = sprintf("%.1f", (($data_size*$count)/elapsed)/(1024*1024))
puts "\nTest completed : #{($count/elapsed).ceil} sent/received msgs/sec (#{mbytes} MB/sec)\n"
puts "Received #{$received} messages\n"
NATS.stop
end
if false
EM.add_periodic_timer(0.25) do
puts "Outstanding data size is #{NATS.client.get_outbound_data_size}"
end
end
puts "Sending #{$count} messages of size #{$data.size} bytes on [#{$sub}]"
# kick things off..
send_batch
end
================================================
FILE: benchmark/queues_perf.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
$:.unshift File.expand_path('../../lib', __FILE__)
require 'nats/client'
$expected = 100000
$hash = 2500
$sub = 'test'
$qs = 5
$qgroup = 'mycoolgroup'
STDOUT.sync = true
parser = OptionParser.new do |opts|
opts.banner = "Usage: queues_perf [options]"
opts.separator ""
opts.separator "options:"
opts.on("-n ITERATIONS", "iterations to expect (default: #{$expected})") { |iter| $expected = iter.to_i }
opts.on("-s SUBJECT", "Send subject (default: #{$sub})") { |nsub| $sub = nsub }
opts.on("-q QUEUE SUBSCRIBERS", "# subscribers (default: #{$qs})") { |qs| $qs = qs }
end
parser.parse(ARGV)
trap("TERM") { exit! }
trap("INT") { exit! }
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start do
received = 1
(0...$qs).each do
NATS.subscribe($sub, :queue => $qgroup) do
($start = Time.now and puts "Started Receiving..") if (received == 1)
if ((received+=1) == $expected)
puts "\nTest completed : #{($expected/(Time.now-$start)).ceil} msgs/sec.\n"
NATS.stop
end
printf('+') if received.modulo($hash) == 0
end
end
puts "Waiting for #{$expected} messages on [#{$sub}] on #{$qs} queue receivers on group: [#{$qgroup}]"
end
================================================
FILE: benchmark/sub_perf.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
$:.unshift File.expand_path('../../lib', __FILE__)
require 'nats/client'
$expected = 100000
$hash = 2500
$sub = 'test'
STDOUT.sync = true
parser = OptionParser.new do |opts|
opts.banner = "Usage: sub_perf [options]"
opts.separator ""
opts.separator "options:"
opts.on("-n COUNT", "Messages to expect (default: #{$expected})") { |count| $expected = count.to_i }
opts.on("-s SUBJECT", "Send subject (default: #{$sub})") { |sub| $sub = sub }
end
parser.parse(ARGV)
trap("TERM") { exit! }
trap("INT") { exit! }
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start do
received = 1
NATS.subscribe($sub) {
($start = Time.now and puts "Started Receiving..") if (received == 1)
if ((received += 1) == $expected)
puts "\nTest completed : #{($expected/(Time.now-$start)).ceil} msgs/sec.\n"
NATS.stop
end
printf('+') if received.modulo($hash) == 0
}
puts "Waiting for #{$expected} messages on [#{$sub}]"
end
================================================
FILE: benchmark/sublist_perf.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
$:.unshift File.expand_path('../../lib', __FILE__)
require 'nats/server/sublist'
class PerfSublist
@@levels = 5
@@targets = ['derek', 'ruth', 'sam', 'meg', 'brett', 'ben', 'miles', 'bella', 'rex', 'diamond']
@@sublist = Sublist.new()
@@subs = []
def PerfSublist.subsInit(pre=nil)
@@targets.each {|t|
sub = pre ? (pre + "." + t) : t
@@sublist.insert(sub, sub)
@@subs << sub
subsInit(sub) if sub.split(".").size < @@levels
}
end
def PerfSublist.disableSublistCache
@@sublist.disable_cache
end
def PerfSublist.addWildcards
@@sublist.insert("ruth.>", "honey")
@@sublist.insert("ruth.sam.meg.>", "honey")
@@sublist.insert("ruth.*.meg.*", "honey")
end
def PerfSublist.matchTest(subject, loop)
start = Time.now
loop.times {@@sublist.match(subject)}
stop = Time.now
puts "Matched #{subject} #{loop} times in #{ms_time(stop-start)} ms @ #{(loop/(stop-start)).to_i}/sec"
end
def PerfSublist.reset
@@sublist = Sublist.new()
end
def PerfSublist.removeAll
@@subs.each do |sub|
@@sublist.remove(sub, sub)
end
end
def PerfSublist.subscriptionCount
@@sublist.count
end
def PerfSublist.totalCount
@@subs.count
end
end
def ms_time(t)
"%0.2f" % (t*1000)
end
# setup the subscription list.
start = Time.now
PerfSublist.subsInit
PerfSublist.addWildcards
stop = Time.now
puts
puts "Sublist holding #{PerfSublist.subscriptionCount} subscriptions"
puts "Insert rate of #{(PerfSublist.subscriptionCount/(stop-start)).to_i}/sec"
#require 'profiler'
puts
puts "cache test"
#Profiler__::start_profile
PerfSublist.matchTest("derek.sam.meg.ruth", 100000)
PerfSublist.matchTest("ruth.sam.meg.derek", 100000) # multiple returns w/ wc
PerfSublist.matchTest("derek.sam.meg.billybob", 100000) # worst case miss
#Profiler__::stop_profile
#Profiler__::print_profile($stdout)
puts
puts "Hit any key to continue w/ cache disabled"
STDIN.getc
#Profiler__::start_profile
PerfSublist.disableSublistCache
PerfSublist.matchTest("derek.sam.meg.ruth", 50000)
PerfSublist.matchTest("ruth.sam.meg.derek", 50000) # multiple returns w/ wc
PerfSublist.matchTest("derek.sam.meg.billybob", 50000) # worst case miss
#Profiler__::stop_profile
#Profiler__::print_profile($stdout)
#Run multiple times to see Jruby speedup
0.times do
puts "\n\n"
#PerfSublist.reset
#PerfSublist.subsInit
#PerfSublist.disableSublistCache
PerfSublist.matchTest("derek.sam.meg.ruth", 50000)
PerfSublist.matchTest("ruth.sam.meg.derek", 50000) # multiple returns w/ wc
PerfSublist.matchTest("derek.sam.meg.billybob", 50000) # worst case miss
end
start = Time.now
PerfSublist.removeAll
stop = Time.now
puts
puts "Sublist now holding #{PerfSublist.subscriptionCount} subscriptions"
puts "Removal rate of #{(PerfSublist.totalCount/(stop-start)).to_i}/sec"
# Allows you to see memory usage, etc
puts
puts "Hit any key to quit"
STDIN.getc
================================================
FILE: bin/nats-pub
================================================
#!/usr/bin/env ruby
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
require 'rubygems'
require 'nats/client'
def usage
puts "Usage: nats-pub <subject> <msg> [-s server] [--creds CREDS]"; exit
end
args = ARGV.dup
opts_parser = OptionParser.new do |opts|
opts.on('-s SERVER') { |server| $nats_server = server }
opts.on('--creds CREDS') { |creds| $creds = creds }
end
args = opts_parser.parse!(args)
subject, msg = args
usage unless subject
msg ||= 'Hello World'
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start($nats_server, user_credentials: $creds) do
NATS.publish(subject, msg) { NATS.stop }
end
puts "Published [#{subject}] : '#{msg}'"
================================================
FILE: bin/nats-queue
================================================
#!/usr/bin/env ruby
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
require 'rubygems'
require 'nats/client'
['TERM', 'INT'].each { |s| trap(s) { puts; exit! } }
def usage
puts "Usage: nats-queue <subject> <queue name> [-s server] [-t] [-r]"; exit
end
args = ARGV.dup
opts_parser = OptionParser.new do |opts|
opts.on('-s SERVER') { |server| $nats_server = server }
opts.on('-t','--time') { $show_time = true }
opts.on('-r','--raw') { $show_raw = true }
opts.on('--creds CREDS') { |creds| $creds = creds }
end
args = opts_parser.parse!(args)
subject, queue_group = args
usage unless subject and queue_group
def time_prefix
"[#{Time.now}] " if $show_time
end
def header
$i=0 unless $i
"#{time_prefix}[\##{$i+=1}]"
end
def decorate sub, msg
if $show_raw
msg
else
"#{header} Received on [#{sub}] : '#{msg}'"
end
end
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start($nats_server, user_credentials: $creds) do
puts "Listening on [#{subject}], queue group [#{queue_group}]" unless $show_raw
NATS.subscribe(subject, :queue => queue_group) { |msg, _, sub|
puts decorate(sub, msg)
}
end
================================================
FILE: bin/nats-request
================================================
#!/usr/bin/env ruby
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
require 'rubygems'
require 'nats/client'
['TERM', 'INT'].each { |s| trap(s) { puts; exit! } }
def usage
puts "Usage: nats-request <subject> <msg> [-s server] [-t] [-r] [-n responses]"; exit
end
args = ARGV.dup
opts_parser = OptionParser.new do |opts|
opts.on('-s SERVER') { |server| $nats_server = server }
opts.on('-t','--time') { $show_time = true }
opts.on('-r','--raw') { $show_raw = true }
opts.on('-n RESPONSES') { |responses| $responses = Integer(responses) if Integer(responses) > 0 }
opts.on('--creds CREDS') { |creds| $creds = creds }
end
args = opts_parser.parse!(args)
subject, msg = args
usage unless subject
msg ||= 'Hello World'
def time_prefix
"[#{Time.now}] " if $show_time
end
def header
$i=0 unless $i
"#{time_prefix}[\##{$i+=1}]"
end
def decorate msg
if $show_raw
msg
else
"#{header} Replied with : '#{msg}'"
end
end
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start($nats_server, user_credentials: $creds) do
NATS.request(subject, msg) { |(msg, reply)|
puts decorate(msg)
exit! if $responses && ($responses-=1) < 1
}
end
================================================
FILE: bin/nats-server
================================================
#!/usr/bin/env ruby
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# NATS command line interface script.
# Run <tt>nats-server -h</tt> to get more usage.
require 'nats/server'
================================================
FILE: bin/nats-sub
================================================
#!/usr/bin/env ruby
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
require 'rubygems'
require 'nats/client'
['TERM', 'INT'].each { |s| trap(s) { puts; exit! } }
def usage
puts "Usage: nats-sub <subject> [-s server] [--creds CREDS] [-t] [-r]"; exit
end
args = ARGV.dup
opts_parser = OptionParser.new do |opts|
opts.on('-s SERVER') { |server| $nats_server = server }
opts.on('-t','--time') { $show_time = true }
opts.on('-r','--raw') { $show_raw = true }
opts.on('--creds CREDS') { |creds| $creds = creds }
end
args = opts_parser.parse!(args)
subject = args.shift
usage unless subject
def time_prefix
"[#{Time.now}] " if $show_time
end
def header
$i=0 unless $i
"#{time_prefix}[\##{$i+=1}]"
end
def decorate sub, msg
if $show_raw
msg
else
"#{header} Received on [#{sub}] : '#{msg}'"
end
end
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start($nats_server, user_credentials: $creds) do
puts "Listening on [#{subject}]" unless $show_raw
NATS.subscribe(subject) { |msg, _, sub| puts decorate(sub, msg) }
end
================================================
FILE: bin/nats-top
================================================
#!/usr/bin/env ruby
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'optparse'
require 'net/http'
require 'uri'
require 'io/wait'
require 'rubygems'
require 'json'
def usage
puts "Usage: nats-top [-s server_uri] [-m local monitor port] [-n num_connections] [-d delay_secs] [--sort sort_by]"
puts "--sort_options for more help"
exit
end
$valid_sort_options = ['pending_size', 'msgs_to', 'msgs_from', 'bytes_to', 'bytes_from', 'subs']
def sort_options_help
puts "Available sort_by options: #{$valid_sort_options.join(', ')}."
puts "E.g. #{$0} -s bytes_to"
exit
end
args = ARGV.dup
opts_parser = OptionParser.new do |opts|
opts.on('-s server_uri') { |server| $nats_server = server }
opts.on('-m local_port') { |port| $nats_port = port.to_i }
opts.on('-n num_connections') { |num| $num_connections = num.to_i }
opts.on('-d delay') { |delay| $delay = delay.to_f }
opts.on('--sort sort_by') { |sort_key| $sort_key = sort_key }
opts.on('--sort_options') { sort_options_help }
opts.on('-h') { usage }
opts.on('--help') { usage }
end
args = opts_parser.parse!(args)
DEFAULT_MONITOR_PORT = 9222
DEFAULT_NUM_CONNECTIONS = 10
DEFAULT_DELAY = 1 #sec
DEFAULT_SORT = 'pending_size'
$nats_port = DEFAULT_MONITOR_PORT if $nats_port.nil?
$num_connections = DEFAULT_NUM_CONNECTIONS if $num_connections.nil?
$nats_server = "http://localhost:#{$nats_port}" if $nats_server.nil?
$nats_server = "http://#{$nats_server}" unless $nats_server.start_with?('http')
$delay = DEFAULT_DELAY if $delay.nil?
$sort_key = DEFAULT_SORT if $sort_key.nil?
$sort_key.downcase!
unless $valid_sort_options.include?($sort_key)
puts "Invalid sort_by argument: #{$sort_key}"
sort_options_help
end
varz_uri = URI.parse("#{$nats_server}/varz")
connz_uri = URI.parse("#{$nats_server}/connz?n=#{$num_connections}&s=#{$sort_key}")
def psize(size, prec=1)
return 'NA' unless size
return sprintf("%.#{prec}f", size) if size < 1024
return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
end
def clear_screen
print "\e[H\e[2J"
end
['TERM', 'INT'].each { |s| trap(s) { clear_screen; exit! } }
in_last_msgs = in_last_bytes = 0
out_last_msgs = out_last_bytes = 0
poll = Time.now
first = true
while true
begin
varz_response = Net::HTTP::get_response(varz_uri)
varz = JSON.parse(varz_response.body, :symbolize_keys => true, :symbolize_names => true)
# Simple rates
delta_in_msgs, in_last_msgs = varz[:in_msgs] - in_last_msgs, varz[:in_msgs]
delta_in_bytes, in_last_bytes = varz[:in_bytes] - in_last_bytes, varz[:in_bytes]
delta_out_msgs, out_last_msgs = varz[:out_msgs] - out_last_msgs, varz[:out_msgs]
delta_out_bytes, out_last_bytes = varz[:out_bytes] - out_last_bytes, varz[:out_bytes]
now = Time.now
tdelta, poll = now - poll, now
unless first
rate_in_msgs = delta_in_msgs / tdelta
rate_in_bytes = delta_in_bytes / tdelta
rate_out_msgs = delta_out_msgs / tdelta
rate_out_bytes = delta_out_bytes / tdelta
end
connz_response = Net::HTTP::get_response(connz_uri)
connz = JSON.parse(connz_response.body, :symbolize_keys => true, :symbolize_names => true)
clear_screen
puts "\nServer:"
puts " Load: CPU: #{varz[:cpu]}% Memory: #{psize(varz[:mem])}"
print " In: Msgs: #{psize(varz[:in_msgs])} Bytes: #{psize(varz[:in_bytes])}"
puts " Msgs/Sec: #{psize(rate_in_msgs)} Bytes/Sec: #{psize(rate_in_bytes)}"
print " Out: Msgs: #{psize(varz[:out_msgs])} Bytes: #{psize(varz[:out_bytes])}"
puts " Msgs/Sec: #{psize(rate_out_msgs)} Bytes/Sec: #{psize(rate_out_bytes)}"
puts "\nConnections: #{psize(connz[:num_connections], 0)}"
conn_t = " %-20s %-8s %-6s %-10s %-10s %-10s %-10s %-10s\n"
printf(conn_t, 'HOST', 'CID', 'SUBS', 'PENDING', 'MSGS_TO', 'MSGS_FROM', 'BYTES_TO', 'BYTES_FROM')
connz[:connections].each do |conn|
printf(conn_t, "#{conn[:ip]}:#{conn[:port]}",
conn[:cid],
psize(conn[:subscriptions]),
psize(conn[:pending_size]),
psize(conn[:out_msgs]),
psize(conn[:in_msgs]),
psize(conn[:out_bytes]),
psize(conn[:in_bytes])
)
end
puts
first = false
sleep($delay)
rescue => e
puts "Error: #{e}"
exit(1)
end
end
================================================
FILE: dependencies.md
================================================
# External Dependencies
This file lists the dependencies used in this repository.
| Dependency | License |
|-|-|
| github.com/eventmachine/eventmachine | Ruby License |
================================================
FILE: examples/auth_pub.rb
================================================
require 'rubygems'
require 'nats/client'
def usage
puts "Usage: pub.rb <user> <pass> <subject> <msg>"; exit
end
user, pass, subject, msg = ARGV
usage unless user and pass and subject
# Default
msg ||= 'Hello World'
uri = "nats://#{user}:#{pass}@localhost:#{NATS::DEFAULT_PORT}"
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start(:uri => uri) do
NATS.publish(subject, msg) { NATS.stop }
end
puts "Published on [#{subject}] : '#{msg}'"
================================================
FILE: examples/auth_sub.rb
================================================
require 'rubygems'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { NATS.stop } }
def usage
puts "Usage: auth_sub <user> <pass> <subject>"; exit
end
user, pass, subject = ARGV
usage unless user and pass and subject
uri = "nats://#{user}:#{pass}@localhost:#{NATS::DEFAULT_PORT}"
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start(:uri => uri) do
puts "Listening on [#{subject}]"
NATS.subscribe(subject) { |msg, _, sub| puts "Received on [#{sub}] : '#{msg}'" }
end
================================================
FILE: examples/auto_unsub.rb
================================================
require 'rubygems'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { NATS.stop } }
def usage
puts "Usage: ruby auto_unsub.rb <subject> [wanted=5] [send=10]"; exit
end
subject = ARGV.shift
wanted = ARGV.shift || 5
send = ARGV.shift || 10
usage unless subject
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
received = 0
NATS.start do
puts "Listening on [#{subject}], auto unsubscribing after #{wanted} messages, but will send #{send}."
NATS.subscribe(subject, :max => wanted) { |msg|
puts "Received '#{msg}'"
received += 1
}
(0...send).each { NATS.publish(subject, 'hello') }
NATS.publish('done') { NATS.stop }
end
puts "Received #{received} messages"
================================================
FILE: examples/busy_body.rb
================================================
require 'rubygems'
require 'nats/client'
# This is an example to show off nats-top. Run busy_body on a monitor enabled
# server and exec nats-top.
['TERM', 'INT'].each { |sig| trap(sig) { exit! } }
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
def create_subscribers(sub='foo.bar', num_subs=10, num_connections=20)
(1..num_connections).each do
NATS.connect do |nc|
(1..num_subs).each { nc.subscribe(sub) }
end
end
end
def create_publishers(sub='foo.bar', body='Hello World!', num_connections=20, num_sends=100)
(1..num_connections).each do
NATS.connect do |nc|
(1..num_sends).each { nc.publish(sub, body) }
end
end
end
def timed_publish(sub='foo.bar', body='Hello World!', delay=1, burst=500)
EM.add_periodic_timer(1) do
b = (burst * rand).to_i
(1..b).each { NATS.publish(sub, body) }
end
end
NATS.start {
create_subscribers
create_publishers
timed_publish
}
================================================
FILE: examples/drain_connection.rb
================================================
require 'nats/client'
nc1 = nil
nc2 = nil
responses = []
inbox = NATS.create_inbox
["TERM", "INT"].each { |sig| trap(sig) {
EM.stop
}
}
subscribers = []
EM.run do
5.times do |n|
subscribers << NATS.connect(drain_timeout: 30, name: "client-#{n}") do |nc|
nc.on_error { |err| puts "#{Time.now} - Error: #{err}" }
nc.on_close { |err| puts "#{Time.now} - Connection drained and closed!" }
puts "#{Time.now} - Started Connection #{n}..."
nc.flush do
nc.subscribe('foo', queue: "workers") do |msg, reply, sub|
nc.publish(reply, "ACK1:#{msg}")
end
nc.subscribe('bar', queue: "workers") do |msg, reply, sub|
nc.publish(reply, "ACK1:#{msg}")
end
nc.subscribe('quux', queue: "workers") do |msg, reply, sub|
nc.publish(reply, "ACK1:#{msg}")
end
end
end
end
pub_client = NATS.connect do |nc|
EM.add_periodic_timer(0.001) do
Fiber.new do
response = nc.request("foo", "A")
puts "Dropped request!!!" if response.nil?
end.resume
end
EM.add_periodic_timer(0.001) do
Fiber.new do
response = nc.request("bar", "B")
puts "Dropped request!!!" if response.nil?
# puts "Response on 'bar' : #{response}"
end.resume
end
EM.add_periodic_timer(0.001) do
Fiber.new do
response = nc.request("quux", "C")
puts "Dropped request!!!" if response.nil?
end.resume
end
end
EM.add_timer(1) do
# Drain is like stop but gracefully closes the connection.
subs = subscribers[0..3]
subs.each_with_index do |nc, i|
if nc.draining?
puts "Already draining... #{responses.count}"
next
end
# Just using close will cause some requests to fail
# nc.close
# Drain is more graceful and allow clients to process requests
# that have already been delivered by the server to the subscriber.
puts "#{Time.now} - Start draining #{nc.options[:name]}... (pending_data: #{nc.pending_data_size})"
nc.drain do
puts "#{Time.now} - Done draining #{nc.options[:name]}!"
end
end
end
end
================================================
FILE: examples/expected.rb
================================================
require 'rubygems'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { NATS.stop } }
def usage
puts "Usage: ruby expected.rb <subject> [timeout (default 5 secs)] [expected (default 5)]"
exit
end
subject = ARGV.shift
timeout = ARGV.shift || 5
expected = ARGV.shift || 5
usage unless subject
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start do
received = 0
puts "Listening on [#{subject}]"
puts "Will timeout in #{timeout} seconds unless #{expected} messages are received."
sid = NATS.subscribe(subject) { |msg|
puts "Received '#{msg}'"
received += 1
if received >= expected
puts "All #{expected} messages received, exiting.."
NATS.stop
end
}
NATS.timeout(sid, timeout, :expected => expected) {
puts "Timedout waiting for a message!"
NATS.stop
}
end
================================================
FILE: examples/fiber_request.rb
================================================
require 'fiber'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { EM.stop } }
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start {
NATS.subscribe('help') do |msg, reply|
puts "[Received]: <<- #{msg}"
NATS.publish(reply, "I'll help! - #{msg}")
end
NATS.subscribe('slow') do |msg, reply|
puts "[Received]: <<- #{msg}"
EM.add_timer(1) { NATS.publish(reply, "I'll help! - #{msg}") }
end
10.times do |n|
NATS.subscribe('hi') do |msg, reply|
NATS.publish(reply, "Hello World! - id:#{n}")
end
end
Fiber.new do
# Requests work synchronously within the same Fiber
# returning the message when done.
response = NATS.request('help', 'foo')
puts "[Response]: ->> '#{response}'"
# Specifying a custom timeout to give up waiting for
# a response.
response = NATS.request('slow', 'bar', timeout: 2)
if response.nil?
puts "No response after 2 seconds..."
else
puts "[Response]: ->> '#{response}'"
end
# Can gather multiple responses with the same request
# which will then return a collection with the responses
# that were received before the timeout.
responses = NATS.request('hi', 'quux', max: 10, timeout: 1)
responses.each_with_index do |response, i|
puts "[Response# #{i}]: ->> '#{response}'"
end
# If no replies then an empty collection is returned.
responses = NATS.request('nowhere', '', max: 10, timeout: 2)
if responses.any?
puts "Got #{responses.count} responses"
else
puts "No response after 2 seconds..."
end
NATS.stop
end.resume
# Multiple fibers can make requests concurrently
# under the same Eventmachine loop.
Fiber.new do
10.times do |n|
response = NATS.request('help', "help.#{n}")
puts "[Response]: ->> '#{response}'"
end
end.resume
}
================================================
FILE: examples/multi_connection.rb
================================================
require 'rubygems'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { NATS.stop } }
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start {
NATS.subscribe('test') do |msg, reply, sub|
puts "received data on sub:#{sub} - #{msg}"
NATS.stop
end
# Form a second connection to send message on
NATS.connect { |nc| nc.publish('test', 'Hello World!') }
}
================================================
FILE: examples/pub.rb
================================================
require 'rubygems'
require 'nats/client'
def usage
puts "Usage: ruby pub.rb <subject> <msg>"; exit
end
subject, msg = ARGV
usage unless subject
msg ||= 'Hello World'
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start { NATS.publish(subject, msg) { NATS.stop } }
puts "Published [#{subject}] : '#{msg}'"
================================================
FILE: examples/queue_sub.rb
================================================
require 'rubygems'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { NATS.stop } }
def usage
puts "Usage: ruby queue_sub.rb <subject> <queue name>"; exit
end
subject, queue_group = ARGV
usage unless subject and queue_group
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start do
puts "Listening on [#{subject}], queue group [#{queue_group}]"
NATS.subscribe(subject, :queue => queue_group) { |msg| puts "Received '#{msg}'" }
end
================================================
FILE: examples/request.rb
================================================
require 'rubygems'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { NATS.stop } }
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start {
# The helper
NATS.subscribe('help') do |msg, reply|
NATS.publish(reply, "I'll help!")
end
# Help request
NATS.request('help') { |response|
puts "Got a response: '#{response}'"
NATS.stop
}
}
================================================
FILE: examples/server_config.yml
================================================
---
#
# Sample Server Sonfiguration
# nats-server -c ./server_config.yml
#
port: 4242
net: localhost
authorization:
user: derek
password: bella
token: deadbeef
timeout: 1
ssl: false
pid_file: '/tmp/nats_test.pid'
# log_file: '/tmp/nats_test.log'
# Debug Options
logtime: true
debug: false
trace: false
# Protocol/Limits
max_control_line: 512
max_payload: 512000
max_pending: 2000000
# EM/IO
no_epoll: false
no_kqueue: true
================================================
FILE: examples/server_config_cluster.yml
================================================
---
#
# Sample Server Sonfiguration
# nats-server -c ./server_config.yml
#
port: 4242
net: localhost
authorization:
user: derek
password: bella
token: deadbeef
timeout: 1
# This is the cluster definition for NATS.
#
# NATS can support both full mesh and directive
# acyclic graphs setups. Its up to the configuration
# setup to avoid cycles.
#
# The port definition allows us to receive incoming connections.
# Comment out if you want to suppress incoming connections.
#
# The server can solicit active connections via the routes definitions below.
#
# authorization is similar to client connection definitions.
cluster:
port: 4244
authorization:
user: route_user
password: cafebabe
token: deadbeef
timeout: 1
# These are actively connected from this server. Other servers
# can connect to us if they supply the correct credentials from
# above.
routes:
nats-route://foo:bar@127.0.0.1:4220
nats-route://foo:bar@127.0.0.1:4221
pid_file: '/tmp/nats_test.pid'
# log_file: '/tmp/nats_test.log'
# Debug Options
logtime: true
debug: false
trace: false
# Protocol/Limits
max_control_line: 512
max_payload: 512000
max_pending: 2000000
# EM/IO
no_epoll: false
no_kqueue: true
================================================
FILE: examples/simple.rb
================================================
require 'rubygems'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { NATS.stop } }
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start {
NATS.subscribe('test') do |msg, reply, sub|
puts "received data on sub:#{sub} - #{msg}"
NATS.stop
end
NATS.publish('test', 'Hello World!')
}
================================================
FILE: examples/sub.rb
================================================
require 'rubygems'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { NATS.stop } }
def usage
puts "Usage: ruby sub.rb <subject>"; exit
end
subject = ARGV.shift
usage unless subject
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start do
puts "Listening on [#{subject}]"
NATS.subscribe(subject) { |msg| puts "Received '#{msg}'" }
end
================================================
FILE: examples/sub_timeout.rb
================================================
require 'rubygems'
require 'nats/client'
["TERM", "INT"].each { |sig| trap(sig) { NATS.stop } }
def usage
puts "Usage: ruby subtimeout.rb <subject> [timeout (default 5 secs)]"; exit
end
subject = ARGV.shift
timeout = ARGV.shift || 5
usage unless subject
NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
NATS.start do
puts "Listening on [#{subject}]"
puts "Will timeout in #{timeout} seconds."
sid = NATS.subscribe(subject) { |msg|
puts "Received '#{msg}'"
NATS.stop
}
NATS.timeout(sid, timeout) {
puts "Timedout waiting for a message!"
NATS.stop
}
end
================================================
FILE: examples/tls-connect.rb
================================================
require 'nats/client'
EM.run do
options = {
:servers => [
'nats://secret:deadbeef@127.0.0.1:4443',
'nats://secret:deadbeef@127.0.0.1:4444'
],
:max_reconnect_attempts => 10,
:reconnect_time_wait => 2,
:tls => {
:private_key_file => './spec/configs/certs/key.pem',
:cert_chain_file => './spec/configs/certs/server.pem'
}
}
NATS.connect(options) do |nc|
puts "#{Time.now.to_f} - Connected to NATS at #{nc.connected_server}"
nc.subscribe("hello") do |msg|
puts "#{Time.now.to_f} - Received: #{msg}"
end
nc.flush do
nc.publish("hello", "world")
end
EM.add_periodic_timer(0.1) do
next unless nc.connected?
nc.publish("hello", "hello")
end
# Set default callbacks
nc.on_error do |e|
puts "#{Time.now.to_f } - Error: #{e}"
end
nc.on_disconnect do |reason|
puts "#{Time.now.to_f} - Disconnected: #{reason}"
end
nc.on_reconnect do |nc|
puts "#{Time.now.to_f} - Reconnected to NATS server at #{nc.connected_server}"
end
nc.on_close do
puts "#{Time.now.to_f} - Connection to NATS closed"
EM.stop
end
end
end
================================================
FILE: examples/tls.rb
================================================
require 'nats/client'
options = {
:servers => [
'nats://secret:deadbeef@127.0.0.1:4443',
'nats://secret:deadbeef@127.0.0.1:4444'
],
:max_reconnect_attempts => 10,
:reconnect_time_wait => 2,
:tls => {
:ssl_version => :TLSv1_2,
:protocols => [:tlsv1_2],
:private_key_file => './spec/configs/certs/key.pem',
:cert_chain_file => './spec/configs/certs/server.pem'
}
}
# Set default callbacks
NATS.on_error do |e|
puts "#{Time.now.to_f } - Error: #{e}"
end
NATS.on_disconnect do |reason|
puts "#{Time.now.to_f} - Disconnected: #{reason}"
end
NATS.on_reconnect do |nats|
puts "#{Time.now.to_f} - Reconnected to NATS server at #{nats.connected_server}"
end
NATS.on_close do
puts "#{Time.now.to_f} - Connection to NATS closed"
EM.stop
end
NATS.start(options) do |nats|
puts "#{Time.now.to_f} - Connected to NATS at #{nats.connected_server}"
nats.subscribe("hello") do |msg|
puts "#{Time.now.to_f} - Received: #{msg}"
end
nats.flush do
nats.publish("hello", "world")
end
EM.add_periodic_timer(0.1) do
next unless nats.connected?
nats.publish("hello", "hello")
end
end
================================================
FILE: lib/nats/client.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'uri'
require 'securerandom'
require 'fiber'
require 'openssl' unless defined?(OpenSSL)
ep = File.expand_path(File.dirname(__FILE__))
require "#{ep}/ext/em"
require "#{ep}/ext/bytesize"
require "#{ep}/ext/json"
require "#{ep}/version"
require "#{ep}/nuid"
module NATS
DEFAULT_PORT = 4222
DEFAULT_URI = "nats://localhost:#{DEFAULT_PORT}".freeze
MAX_RECONNECT_ATTEMPTS = 10
RECONNECT_TIME_WAIT = 2
MAX_PENDING_SIZE = 32768
# Maximum outbound size per client to trigger FP, 20MB
FAST_PRODUCER_THRESHOLD = (10*1024*1024)
# Ping intervals
DEFAULT_PING_INTERVAL = 120
DEFAULT_PING_MAX = 2
# Drain mode support
DEFAULT_DRAIN_TIMEOUT = 30
# Protocol
# @private
MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i #:nodoc:
OK = /\A\+OK\s*\r\n/i #:nodoc:
ERR = /\A-ERR\s+('.+')?\r\n/i #:nodoc:
PING = /\APING\s*\r\n/i #:nodoc:
PONG = /\APONG\s*\r\n/i #:nodoc:
INFO = /\AINFO\s+([^\r\n]+)\r\n/i #:nodoc:
UNKNOWN = /\A(.*)\r\n/ #:nodoc:
# Responses
CR_LF = ("\r\n".freeze) #:nodoc:
CR_LF_SIZE = (CR_LF.bytesize) #:nodoc:
PING_REQUEST = ("PING#{CR_LF}".freeze) #:nodoc:
PONG_RESPONSE = ("PONG#{CR_LF}".freeze) #:nodoc:
SUB_OP = ('SUB'.freeze) #:nodoc:
EMPTY_MSG = (''.freeze) #:nodoc:
# Used for future pedantic Mode
SUB = /^([^\.\*>\s]+|>$|\*)(\.([^\.\*>\s]+|>$|\*))*$/ #:nodoc:
SUB_NO_WC = /^([^\.\*>\s]+)(\.([^\.\*>\s]+))*$/ #:nodoc:
# Parser
AWAITING_CONTROL_LINE = 1 #:nodoc:
AWAITING_MSG_PAYLOAD = 2 #:nodoc:
AWAITING_INFO_LINE = 3 # :nodoc:
class Error < StandardError; end #:nodoc:
# When the NATS server sends us an ERROR message, this is raised/passed by default
class ServerError < Error; end #:nodoc:
# When we detect error on the client side (e.g. Fast Producer, TLS required)
class ClientError < Error; end #:nodoc:
# When we cannot connect to the server (either initially or after a reconnect), this is raised/passed
class ConnectError < Error; end #:nodoc:
# When we cannot connect to the server because authorization failed.
class AuthError < ConnectError; end #:nodoc:
class << self
attr_reader :client, :reactor_was_running, :err_cb, :err_cb_overridden #:nodoc:
attr_reader :reconnect_cb, :close_cb, :disconnect_cb #:nodoc
alias :reactor_was_running? :reactor_was_running
# Create and return a connection to the server with the given options.
# The optional block will be called when the connection has been completed.
#
# @param [String] uri The URI or comma separated list of URIs of NATS servers to connect to.
# @param [Hash] opts
# @option opts [String|URI] :uri The URI to connect to, example nats://localhost:4222
# @option opts [Boolean] :reconnect Boolean that can be used to suppress reconnect functionality.
# @option opts [Boolean] :debug Boolean that can be used to output additional debug information.
# @option opts [Boolean] :verbose Boolean that is sent to server for setting verbose protocol mode.
# @option opts [Boolean] :pedantic Boolean that is sent to server for setting pedantic mode.
# @option opts [Boolean] :ssl Boolean that is sent to server for setting TLS/SSL mode.
# @option opts [Hash] :tls Map of options for configuring secure connection handled to EM#start_tls directly.
# @option opts [Integer] :max_reconnect_attempts Integer that can be used to set the max number of reconnect tries
# @option opts [Integer] :reconnect_time_wait Integer that can be used to set the number of seconds to wait between reconnect tries
# @option opts [Integer] :ping_interval Integer that can be used to set the ping interval in seconds.
# @option opts [Integer] :max_outstanding_pings Integer that can be used to set the max number of outstanding pings before declaring a connection closed.
# @param [Block] &blk called when the connection is completed. Connection will be passed to the block.
# @return [NATS] connection to the server.
#
# @example Connect to local NATS server.
# NATS.connect do |nc|
# # ...
# end
#
# @example Setting custom server URI to connect.
# NATS.connect("nats://localhost:4222") do |nc|
# # ...
# end
#
# @example Setting username and password to authenticate.
# NATS.connect("nats://user:password@localhost:4222") do |nc|
# # ...
# end
#
# @example Specifying explicit list of servers via options.
# NATS.connect(servers: ["nats://127.0.0.1:4222","nats://127.0.0.1:4223","nats://127.0.0.1:4224"]) do |nc|
# # ...
# end
#
# @example Using comma separated array to define list of servers.
# NATS.connect("nats://localhost:4223,nats://localhost:4224") do |nc|
# # ...
# end
#
# @example Only specifying endpoint uses NATS default scheme and port.
# NATS.connect("demo.nats.io") do |nc|
# # ...
# end
#
# @example Setting infinite reconnect retries with 2 seconds back off against custom URI.
# NATS.connect("demo.nats.io:4222", max_reconnect_attempts: -1, reconnect_time_wait: 2) do |nc|
# # ...
# end
#
def connect(uri=nil, opts={}, &blk)
case uri
when String
# Initialize TLS defaults in case any url is using it.
uris = opts[:uri] = process_uri(uri)
opts[:tls] ||= {} if uris.any? {|u| u.scheme == 'tls'}
when Hash
opts = uri
end
# Defaults
opts[:verbose] = false if opts[:verbose].nil?
opts[:pedantic] = false if opts[:pedantic].nil?
opts[:reconnect] = true if opts[:reconnect].nil?
opts[:ssl] = false if opts[:ssl].nil?
opts[:max_reconnect_attempts] = MAX_RECONNECT_ATTEMPTS if opts[:max_reconnect_attempts].nil?
opts[:reconnect_time_wait] = RECONNECT_TIME_WAIT if opts[:reconnect_time_wait].nil?
opts[:ping_interval] = DEFAULT_PING_INTERVAL if opts[:ping_interval].nil?
opts[:max_outstanding_pings] = DEFAULT_PING_MAX if opts[:max_outstanding_pings].nil?
opts[:drain_timeout] = DEFAULT_DRAIN_TIMEOUT if opts[:drain_timeout].nil?
# Override with ENV
opts[:uri] ||= ENV['NATS_URI'] || DEFAULT_URI
opts[:verbose] = ENV['NATS_VERBOSE'].downcase == 'true' unless ENV['NATS_VERBOSE'].nil?
opts[:pedantic] = ENV['NATS_PEDANTIC'].downcase == 'true' unless ENV['NATS_PEDANTIC'].nil?
opts[:debug] = ENV['NATS_DEBUG'].downcase == 'true' unless ENV['NATS_DEBUG'].nil?
opts[:reconnect] = ENV['NATS_RECONNECT'].downcase == 'true' unless ENV['NATS_RECONNECT'].nil?
opts[:fast_producer_error] = ENV['NATS_FAST_PRODUCER'].downcase == 'true' unless ENV['NATS_FAST_PRODUCER'].nil?
opts[:ssl] = ENV['NATS_SSL'].downcase == 'true' unless ENV['NATS_SSL'].nil?
opts[:max_reconnect_attempts] = ENV['NATS_MAX_RECONNECT_ATTEMPTS'].to_i unless ENV['NATS_MAX_RECONNECT_ATTEMPTS'].nil?
opts[:reconnect_time_wait] = ENV['NATS_RECONNECT_TIME_WAIT'].to_i unless ENV['NATS_RECONNECT_TIME_WAIT'].nil?
opts[:name] ||= ENV['NATS_CONNECTION_NAME']
opts[:no_echo] ||= ENV['NATS_NO_ECHO'] || false
opts[:ping_interval] = ENV['NATS_PING_INTERVAL'].to_i unless ENV['NATS_PING_INTERVAL'].nil?
opts[:max_outstanding_pings] = ENV['NATS_MAX_OUTSTANDING_PINGS'].to_i unless ENV['NATS_MAX_OUTSTANDING_PINGS'].nil?
opts[:drain_timeout] ||= ENV['NATS_DRAIN_TIMEOUT'].to_i unless ENV['NATS_DRAIN_TIMEOUT'].nil?
uri = opts[:uris] || opts[:servers] || opts[:uri]
if opts[:tls]
case
when opts[:tls][:ca_file]
# Ensure that the file exists before going further
# in order to report configuration errors during
# connect synchronously.
if !File.readable?(opts[:tls][:ca_file])
raise(Error, "TLS Verification is enabled but ca_file %s is not readable" % opts[:tls][:ca_file])
end
# Certificate is supplied so assume we mean verification by default,
# but still allow disabling explicitly by setting to false.
opts[:tls][:verify_peer] ||= true
when (opts[:tls][:verify_peer] && !opts[:tls][:ca_file])
raise(Error, "TLS Verification is enabled but ca_file is not set")
else
# Otherwise, disable verifying peer by default,
# thus never reaching EM#ssl_verify_peer
opts[:tls][:verify_peer] = false
end
# Allow overriding directly but default to those which server supports.
opts[:tls][:ssl_version] ||= %w(tlsv1 tlsv1_1 tlsv1_2)
opts[:tls][:protocols] ||= %w(tlsv1 tlsv1_1 tlsv1_2)
end
# If they pass an array here just pass along to the real connection, and use first as the first attempt..
# Real connection will do proper walk throughs etc..
unless uri.nil?
uris = uri.kind_of?(Array) ? uri : [uri]
uris.shuffle! unless opts[:dont_randomize_servers]
u = uris.first
@uri = u.is_a?(URI) ? u.dup : URI.parse(u)
end
@err_cb = proc { |e| raise e } unless err_cb
@close_cb = proc { } unless close_cb
@disconnect_cb = proc { } unless disconnect_cb
client = EM.connect(@uri.host, @uri.port, self, opts)
client.on_connect(&blk) if blk
return client
end
# Create a default client connection to the server.
# @see NATS::connect
def start(*args, &blk)
@reactor_was_running = EM.reactor_running?
unless (@reactor_was_running || blk)
raise(Error, "EM needs to be running when NATS.start is called without a run block")
end
# Setup optimized select versions
if EM.epoll?
EM.epoll
elsif EM.kqueue?
EM.kqueue
elsif EM.library_type == :java
# No warning needed, we're using Java NIO
else
Kernel.warn('Neither epoll nor kqueue are supported, performance may be impacted')
end
EM.run { @client = connect(*args, &blk) }
end
# Close the default client connection and optionally call the associated block.
# @param [Block] &blk called when the connection is closed.
def stop(&blk)
client.close if (client and (client.connected? || client.reconnecting?))
blk.call if blk
@err_cb = nil
@close_cb = nil
@reconnect_cb = nil
@disconnect_cb = nil
end
# Drain gracefully disconnects from the server, letting
# subscribers process pending messages already sent by server and
# optionally calls the associated block.
# @param [Block] &blk called when drain is done and connection is closed.
def drain(&blk)
if (client and !client.draining? and (client.connected? || client.reconnecting?))
client.drain { blk.call if blk }
end
end
# @return [URI] Connected server
def connected_server
return nil unless client
client.connected_server
end
# @return [Boolean] Connected state
def connected?
return false unless client
client.connected?
end
# @return [Boolean] Reconnecting state
def reconnecting?
return false unless client
client.reconnecting?
end
# @return [Boolean] Draining state
def draining?
return false unless client
client.draining?
end
# @return [Hash] Options
def options
return {} unless client
client.options
end
# @return [Hash] Server information
def server_info
return nil unless client
client.server_info
end
# Set the default on_error callback.
# @param [Block] &callback called when an error has been detected.
def on_error(&callback)
@err_cb, @err_cb_overridden = callback, true
end
# Set the default on_reconnect callback.
# @param [Block] &callback called when a reconnect attempt is made.
def on_reconnect(&callback)
@reconnect_cb = callback
@client.on_reconnect(&callback) unless @client.nil?
end
# Set the default on_disconnect callback.
# @param [Block] &callback called whenever client disconnects from a server.
def on_disconnect(&callback)
@disconnect_cb = callback
@client.on_disconnect(&callback) unless @client.nil?
end
# Set the default on_closed callback.
# @param [Block] &callback called when will reach a state when will no longer be connected.
def on_close(&callback)
@close_cb = callback
@client.on_close(&callback) unless @client.nil?
end
# Publish a message using the default client connection.
# @see NATS#publish
def publish(*args, &blk)
(@client ||= connect).publish(*args, &blk)
end
# Subscribe using the default client connection.
# @see NATS#subscribe
def subscribe(*args, &blk)
(@client ||= connect).subscribe(*args, &blk)
end
# Cancel a subscription on the default client connection.
# @see NATS#unsubscribe
def unsubscribe(*args)
(@client ||= connect).unsubscribe(*args)
end
# Set a timeout for receiving messages for the subscription.
# @see NATS#timeout
def timeout(*args, &blk)
(@client ||= connect).timeout(*args, &blk)
end
# Publish a message and wait for a response on the default client connection.
# @see NATS#request
def request(*args, &blk)
(@client ||= connect).request(*args, &blk)
end
# Returns a subject that can be used for "directed" communications.
# @return [String]
def create_inbox
"_INBOX.#{SecureRandom.hex(13)}"
end
# Flushes all messages and subscriptions in the default connection
# @see NATS#flush
def flush(*args, &blk)
(@client ||= connect).flush(*args, &blk)
end
# Return bytes outstanding for the default client connection.
# @see NATS#pending_data_size
def pending_data_size(*args)
(@client ||= connect).pending_data_size(*args)
end
def wait_for_server(uri, max_wait = 5) # :nodoc:
start = Time.now
while (Time.now - start < max_wait) # Wait max_wait seconds max
break if server_running?(uri)
sleep(0.1)
end
end
def server_running?(uri) # :nodoc:
require 'socket'
s = TCPSocket.new(uri.host, uri.port)
s.close
return true
rescue
return false
end
def clear_client # :nodoc:
@client = nil
end
private
def uri_is_remote?(uri)
uri.host != 'localhost' && uri.host != '127.0.0.1'
end
def process_uri(uris)
connect_uris = []
uris.split(',').each do |uri|
opts = {}
# Scheme
if uri.include?("://")
scheme, uri = uri.split("://")
opts[:scheme] = scheme
else
opts[:scheme] = 'nats'
end
# UserInfo
if uri.include?("@")
userinfo, endpoint = uri.split("@")
host, port = endpoint.split(":")
opts[:userinfo] = userinfo
else
host, port = uri.split(":")
end
# Host and Port
opts[:host] = host || "localhost"
opts[:port] = port || DEFAULT_PORT
connect_uris << URI::Generic.build(opts)
end
connect_uris
end
end
attr_reader :connected, :connect_cb, :err_cb, :err_cb_overridden, :pongs_received #:nodoc:
attr_reader :closing, :reconnecting, :draining, :server_pool, :options, :server_info #:nodoc
attr_reader :msgs_received, :msgs_sent, :bytes_received, :bytes_sent, :pings
attr_reader :disconnect_cb, :close_cb
alias :connected? :connected
alias :closing? :closing
alias :reconnecting? :reconnecting
alias :draining? :draining
def initialize(options)
@options = options
process_uri_options
@buf = nil
@ssid, @subs = 1, {}
@err_cb = NATS.err_cb
@close_cb = NATS.close_cb
@reconnect_cb = NATS.reconnect_cb
@disconnect_cb = NATS.disconnect_cb
@reconnect_timer, @needed = nil, nil
@connected, @closing, @reconnecting, @conn_cb_called = false, false, false, false
@msgs_received = @msgs_sent = @bytes_received = @bytes_sent = @pings = 0
@pending_size = 0
@server_info = { }
# Mark whether we should be connecting securely, try best effort
# in being compatible with present ssl support.
@ssl = false
@tls = nil
@tls = options[:tls] if options[:tls]
@ssl = options[:ssl] if options[:ssl] or @tls
# New style request/response implementation.
@resp_sub = nil
@resp_map = nil
@resp_sub_prefix = nil
@nuid = NATS::NUID.new
# Drain mode
@draining = false
@drained_subs = false
# NKEYS
@user_credentials = options[:user_credentials] if options[:user_credentials]
@nkeys_seed = options[:nkeys_seed] if options[:nkeys_seed]
@user_nkey_cb = nil
@user_jwt_cb = nil
@signature_cb = nil
# NKEYS
setup_nkeys_connect if @user_credentials or @nkeys_seed
end
# Publish a message to a given subject, with optional reply subject and completion block
# @param [String] subject
# @param [Object, #to_s] msg
# @param [String] opt_reply
# @param [Block] blk, closure called when publish has been processed by the server.
def publish(subject, msg=EMPTY_MSG, opt_reply=nil, &blk)
return unless subject and not @drained_subs
msg = msg.to_s
# Accounting
@msgs_sent += 1
@bytes_sent += msg.bytesize if msg
send_command("PUB #{subject} #{opt_reply} #{msg.bytesize}#{CR_LF}#{msg}#{CR_LF}")
queue_server_rt(&blk) if blk
end
# Subscribe to a subject with optional wildcards.
# Messages will be delivered to the supplied callback.
# Callback can take any number of the supplied arguments as defined by the list: msg, reply, sub.
# Returns subscription id which can be passed to #unsubscribe.
# @param [String] subject, optionally with wilcards.
# @param [Hash] opts, optional options hash, e.g. :queue, :max.
# @param [Block] callback, called when a message is delivered.
# @return [Object] sid, Subject Identifier
def subscribe(subject, opts={}, &callback)
return unless subject and not draining?
sid = (@ssid += 1)
sub = @subs[sid] = { :subject => subject, :callback => callback, :received => 0 }
sub[:queue] = opts[:queue] if opts[:queue]
sub[:max] = opts[:max] if opts[:max]
send_command("SUB #{subject} #{opts[:queue]} #{sid}#{CR_LF}")
# Setup server support for auto-unsubscribe
unsubscribe(sid, opts[:max]) if opts[:max]
sid
end
# Cancel a subscription.
# @param [Object] sid
# @param [Number] opt_max, optional number of responses to receive before auto-unsubscribing
def unsubscribe(sid, opt_max=nil)
return if draining?
opt_max_str = " #{opt_max}" unless opt_max.nil?
send_command("UNSUB #{sid}#{opt_max_str}#{CR_LF}")
return unless sub = @subs[sid]
sub[:max] = opt_max
@subs.delete(sid) unless (sub[:max] && (sub[:received] < sub[:max]))
end
# Drain gracefully closes the connection.
# @param [Block] blk called when drain is done and connection is closed.
def drain(&blk)
return if draining? or closing?
@draining = true
# Remove interest in all subjects to stop receiving messages.
@subs.each do |sid, _|
send_command("UNSUB #{sid} #{CR_LF}")
end
# Roundtrip to ensure no more messages are received.
flush do
drain_timeout_timer, draining_timer = nil, nil
drain_timeout_timer = EM.add_timer(options[:drain_timeout]) do
EM.cancel_timer(draining_timer)
# Report the timeout via the error callback and just close
err_cb.call(NATS::ClientError.new("Drain Timeout"))
@draining = false
close unless closing?
blk.call if blk
end
# Periodically check for the pending data to be empty.
draining_timer = EM.add_periodic_timer(0.1) do
next unless closing? or @buf.nil? or @buf.empty?
# Subscriptions have been drained already so disallow publishing.
@drained_subs = true
next unless pending_data_size == 0
EM.cancel_timer(draining_timer)
EM.cancel_timer(drain_timeout_timer)
# We're done draining and can close now.
@draining = false
close unless closing?
blk.call if blk
end
end
end
# Return the active subscription count.
# @return [Number]
def subscription_count
@subs.size
end
# Setup a timeout for receiving messages for the subscription.
# @param [Object] sid
# @param [Number] timeout, float in seconds
# @param [Hash] opts, options, :auto_unsubscribe(true), :expected(1)
def timeout(sid, timeout, opts={}, &callback)
# Setup a timeout if requested
return unless sub = @subs[sid]
auto_unsubscribe, expected = true, 1
auto_unsubscribe = opts[:auto_unsubscribe] if opts.key?(:auto_unsubscribe)
expected = opts[:expected] if opts.key?(:expected)
EM.cancel_timer(sub[:timeout]) if sub[:timeout]
sub[:timeout] = EM.add_timer(timeout) do
unsubscribe(sid) if auto_unsubscribe
callback.call(sid) if callback
end
sub[:expected] = expected
end
# Send a request and have the response delivered to the supplied callback.
# @param [String] subject
# @param [Object] msg
# @param [Block] callback
# @return [Object] sid
def request(subject, data=nil, opts={}, &cb)
return unless subject
# In case of using async request then fallback to auto unsubscribe
# based request/response and not break compatibility too much since
# new request/response style can only be used with fibers.
if cb
inbox = "_INBOX.#{@nuid.next}"
s = subscribe(inbox, opts) { |msg, reply|
case cb.arity
when 0 then cb.call
when 1 then cb.call(msg)
else cb.call(msg, reply)
end
}
publish(subject, data, inbox)
return s
end
# If this is the first request being made, then need to start
# the responses mux handler that handles the responses.
start_resp_mux_sub! unless @resp_sub_prefix
# Generate unique token for the reply subject.
token = @nuid.next
inbox = "#{@resp_sub_prefix}.#{token}"
# Synchronous request/response requires using a Fiber
# to be able to await the response.
f = Fiber.current
@resp_map[token][:fiber] = f
# If awaiting more than a single response then use array
# to include all that could be gathered before the deadline.
expected = opts[:max] ||= 1
@resp_map[token][:expected] = expected
@resp_map[token][:msgs] = [] if expected > 1
# Announce the request with the inbox using the token.
publish(subject, data, inbox)
# If deadline expires, then discard the token and resume fiber
opts[:timeout] ||= 0.5
t = EM.add_timer(opts[:timeout]) do
if expected > 1
f.resume @resp_map[token][:msgs]
else
f.resume
end
@resp_map.delete(token)
end
# Wait for the response and cancel timeout callback if received.
if expected > 1
# Wait to receive all replies that can get before deadline.
msgs = Fiber.yield
EM.cancel_timer(t)
# Slice and throwaway responses that are not needed.
return msgs.slice(0, expected)
else
msg = Fiber.yield
EM.cancel_timer(t)
return msg
end
end
def start_resp_mux_sub!
@resp_sub_prefix = "_INBOX.#{@nuid.next}"
@resp_map = Hash.new { |h,k| h[k] = { }}
# Single subscription that will be handling all the requests
# using fibers to yield the responses.
subscribe("#{@resp_sub_prefix}.*") do |msg, reply, subject|
token = subject.split('.').last
# Discard the response if requestor not interested already.
next unless @resp_map.key? token
# Take fiber that will be passed the response
f = @resp_map[token][:fiber]
expected = @resp_map[token][:expected]
if expected == 1
f.resume msg
@resp_map.delete(token)
next
end
if @resp_map[token][:msgs].size < expected
@resp_map[token][:msgs] << msg
msgs = @resp_map[token][:msgs]
if msgs.size >= expected
f.resume(msgs)
else
# Wait to gather more messages or timeout.
next
end
end
@resp_map.delete(token)
end
end
# Flushes all messages and subscriptions for the connection.
# All messages and subscriptions have been processed by the server
# when the optional callback is called.
def flush(&blk)
queue_server_rt(&blk) if blk
end
# Define a callback to be called when the client connection has been established.
# @param [Block] callback
def on_connect(&callback)
@connect_cb = callback
end
# Define a callback to be called when errors occur on the client connection.
# @param [Block] &callback called when an error has been detected.
def on_error(&callback)
@err_cb, @err_cb_overridden = callback, true
end
# Define a callback to be called when a reconnect attempt is made.
# @param [Block] &callback called when a reconnect attempt is made.
def on_reconnect(&callback)
@reconnect_cb = callback
end
# Define a callback to be called when client is disconnected from server.
# @param [Block] &callback called whenever client disconnects from a server.
def on_disconnect(&callback)
@disconnect_cb = callback
end
# Define a callback to be called when client is disconnected from server.
# @param [Block] &callback called when will reach a state when will no longer be connected.
def on_close(&callback)
@close_cb = callback
end
# Close the connection to the server.
def close
@closing = true
cancel_ping_timer
cancel_reconnect_timer
close_connection_after_writing if connected?
process_disconnect if reconnecting?
end
# Return bytes outstanding waiting to be sent to server.
def pending_data_size
get_outbound_data_size + @pending_size
end
# Return snapshot of current traffic flow stats in the client.
def stats
{
in_msgs: @msgs_received,
out_msgs: @msgs_sent,
in_bytes: @bytes_received,
out_bytes: @bytes_sent
}.freeze
end
def user_err_cb? # :nodoc:
err_cb_overridden || NATS.err_cb_overridden
end
def auth_connection?
!@uri.user.nil? || @options[:token] || @server_info[:auth_required]
end
def connect_command #:nodoc:
cs = {
:verbose => @options[:verbose],
:pedantic => @options[:pedantic],
:lang => ::NATS::LANG,
:version => ::NATS::VERSION,
:protocol => ::NATS::PROTOCOL_VERSION,
:echo => !@options[:no_echo]
}
case
when @options[:user_credentials]
nonce = @server_info[:nonce]
cs[:jwt] = @user_jwt_cb.call
cs[:sig] = @signature_cb.call(nonce)
when @options[:nkeys_seed]
nonce = @server_info[:nonce]
cs[:nkey] = @user_nkey_cb.call
cs[:sig] = @signature_cb.call(nonce)
when @options[:token]
cs[:auth_token] = @options[:token]
when @uri.password.nil?
cs[:auth_token] = @uri.user
else
cs[:user] = @uri.user
cs[:pass] = @uri.password
end if auth_connection?
cs[:name] = @options[:name] if @options[:name]
cs[:ssl_required] = @ssl if @ssl
cs[:tls_required] = true if @tls
"CONNECT #{cs.to_json}#{CR_LF}"
end
def send_connect_command #:nodoc:
send_command(connect_command, true)
end
def queue_server_rt(&cb) #:nodoc:
return unless cb
(@pongs ||= []) << cb
send_command(PING_REQUEST)
end
def on_msg(subject, sid, reply, msg) #:nodoc:
# Accounting - We should account for inbound even if they are not processed.
@msgs_received += 1
@bytes_received += msg.bytesize if msg
return unless sub = @subs[sid]
# Check for auto_unsubscribe
sub[:received] += 1
if sub[:max]
# Client side support in case server did not receive unsubscribe
return unsubscribe(sid) if (sub[:received] > sub[:max])
# cleanup here if we have hit the max..
@subs.delete(sid) if (sub[:received] == sub[:max])
end
if cb = sub[:callback]
case cb.arity
when 0 then cb.call
when 1 then cb.call(msg)
when 2 then cb.call(msg, reply)
else cb.call(msg, reply, subject)
end
end
# Check for a timeout, and cancel if received >= expected
if (sub[:timeout] && sub[:received] >= sub[:expected])
EM.cancel_timer(sub[:timeout])
sub[:timeout] = nil
end
end
def flush_pending #:nodoc:
return unless @pending
send_data(@pending.join)
@pending, @pending_size = nil, 0
end
def receive_data(data) #:nodoc:
@buf = @buf ? @buf << data : data
while (@buf)
case @parse_state
when AWAITING_INFO_LINE
case @buf
when INFO
@buf = $'
process_connect_init($1)
else
# If we are here we do not have a complete line yet that we understand.
return
end
when AWAITING_CONTROL_LINE
case @buf
when MSG
@buf = $'
@sub, @sid, @reply, @needed = $1, $2.to_i, $4, $5.to_i
@parse_state = AWAITING_MSG_PAYLOAD
when OK # No-op right now
@buf = $'
when ERR
@buf = $'
current = server_pool.first
current[:error_received] = true
if current[:auth_required] && !current[:auth_ok]
err_cb.call(NATS::AuthError.new($1))
else
err_cb.call(NATS::ServerError.new($1))
end
when PING
@pings += 1
@buf = $'
send_command(PONG_RESPONSE)
when PONG
@buf = $'
cb = @pongs.shift
cb.call if cb
when INFO
@buf = $'
process_info($1)
when UNKNOWN
@buf = $'
err_cb.call(NATS::ServerError.new("Unknown protocol: #{$1}"))
else
# If we are here we do not have a complete line yet that we understand.
return
end
@buf = nil if (@buf && @buf.empty?)
when AWAITING_MSG_PAYLOAD
return unless (@needed && @buf.bytesize >= (@needed + CR_LF_SIZE))
on_msg(@sub, @sid, @reply, @buf.slice(0, @needed))
@buf = @buf.slice((@needed + CR_LF_SIZE), @buf.bytesize)
@sub = @sid = @reply = @needed = nil
@parse_state = AWAITING_CONTROL_LINE
@buf = nil if (@buf && @buf.empty?)
end
end
end
def process_connect_init(info) # :nodoc:
# Each JSON parser uses a different key/value pair to use symbol keys
# instead of strings when parsing. Passing all three pairs assures each
# parser gets what it needs. For the json gem :symbolize_name, for yajl
# :symbolize_keys, and for oj :symbol_keys.
@server_info = JSON.parse(info, :symbolize_keys => true, :symbolize_names => true, :symbol_keys => true)
case
when (server_using_secure_connection? and client_using_secure_connection?)
# Allow parameterizing secure connection via EM#start_tls directly if present.
start_tls(@tls || {})
when (server_using_secure_connection? and !client_using_secure_connection?)
# Call unbind since there is a configuration mismatch between client/server
# anyway and communication cannot happen in this state.
err_cb.call(NATS::ClientError.new('TLS/SSL required by server'))
close_connection_after_writing
when (client_using_secure_connection? and !server_using_secure_connection?)
err_cb.call(NATS::ClientError.new('TLS/SSL not supported by server'))
close_connection_after_writing
else
# Otherwise, use a regular connection.
end
# Check whether there no echo is supported by the server.
if @options[:no_echo]
if @server_info[:proto].nil? || @server_info[:proto] < 1
err_cb.call(NATS::ServerError.new('No echo option not supported by this server'))
close_connection_after_writing
end
end
send_connect_command
# Only initial INFO command is treated specially for auth reasons,
# the rest are processed asynchronously to discover servers.
@parse_state = AWAITING_CONTROL_LINE
process_info(info)
process_connect
if @server_info[:auth_required]
current = server_pool.first
current[:auth_required] = true
# Send pending connect followed by ping/pong to ensure we're authorized.
queue_server_rt { current[:auth_ok] = true }
end
flush_pending
end
def process_info(info_line) #:nodoc:
info = JSON.parse(info_line, :symbolize_keys => true, :symbolize_names => true, :symbol_keys => true)
# Detect any announced server that we might not be aware of...
connect_urls = info[:connect_urls]
if connect_urls
srvs = []
connect_urls.each do |url|
u = URI.parse("nats://#{url}")
present = server_pool.detect do |srv|
srv[:uri].host == u.host && srv[:uri].port == u.port
end
if not present
# Let explicit user and pass options set the credentials.
u.user = options[:user] if options[:user]
u.password = options[:pass] if options[:pass]
# Use creds from the current server if not set explicitly.
if @uri and !@uri.user.nil? and !@uri.user.empty?
u.user ||= @uri.user
u.password ||= @uri.password
end
srvs << { :uri => u, :reconnect_attempts => 0, :discovered => true }
end
end
srvs.shuffle! unless @options[:dont_randomize_servers]
# Include in server pool but keep current one as the first one.
server_pool.push(*srvs)
end
info
end
def client_using_secure_connection?
@tls || @ssl
end
def server_using_secure_connection?
@server_info[:ssl_required] || @server_info[:tls_required]
end
def ssl_verify_peer(cert)
incoming = OpenSSL::X509::Certificate.new(cert)
store = OpenSSL::X509::Store.new
store.set_default_paths
store.add_file @options[:tls][:ca_file]
result = store.verify(incoming)
err_cb.call(NATS::ConnectError.new('TLS Verification failed checking issuer based on CA %s' % @options[:tls][:ca_file])) unless result
result
rescue NATS::ConnectError
false
end
def cancel_ping_timer
if @ping_timer
EM.cancel_timer(@ping_timer)
@ping_timer = nil
end
end
def connection_completed #:nodoc:
@parse_state = AWAITING_INFO_LINE
# Delay sending CONNECT or any other command here until we are sure
# that we have a valid established secure connection.
return if (@ssl or @tls)
# Mark that we established already TCP connection to the server,
# when using TLS we only do so after handshake has been completed.
@connected = true
end
def ssl_handshake_completed
@connected = true
end
def process_connect #:nodoc:
# Reset reconnect attempts since TCP connection has been successful at this point.
current = server_pool.first
current[:was_connected] = true
current[:reconnect_attempts] ||= 0
cancel_reconnect_timer if reconnecting?
# Whip through any pending SUB commands since we replay
# all subscriptions already done anyway.
@pending.delete_if { |sub| sub[0..2] == SUB_OP } if @pending
@subs.each_pair { |k, v| send_command("SUB #{v[:subject]} #{v[:queue]} #{k}#{CR_LF}") }
unless user_err_cb? or reconnecting?
@err_cb = proc { |e| raise e }
end
# We have validated the connection at this point so send CONNECT
# and any other pending commands which we need to the server.
flush_pending
if (connect_cb and not @conn_cb_called)
# We will round trip the server here to make sure all state from any pending commands
# has been processed before calling the connect callback.
queue_server_rt do
connect_cb.call(self)
@conn_cb_called = true
end
end
# Notify via reconnect callback that we are again plugged again into the system.
if reconnecting?
@reconnecting = false
@reconnect_cb.call(self) unless @reconnect_cb.nil?
end
# Initialize ping timer and processing
@pings_outstanding = 0
@pongs_received = 0
@ping_timer = EM.add_periodic_timer(@options[:ping_interval]) do
send_ping
end
end
def send_ping #:nodoc:
return if @closing
@pings_outstanding += 1
if @pings_outstanding > @options[:max_outstanding_pings]
close_connection
#close
return
end
queue_server_rt { process_pong }
flush_pending
end
def process_pong
@pongs_received += 1
@pings_outstanding -= 1
end
def should_delay_connect?(server)
case
when server[:was_connected]
server[:reconnect_attempts] >= 0
when server[:last_reconnect_attempt]
(MonotonicTime.now - server[:last_reconnect_attempt]) < @options[:reconnect_time_wait]
else
false
end
end
def schedule_reconnect #:nodoc:
@reconnecting = true
@connected = false
@reconnect_timer = EM.add_timer(@options[:reconnect_time_wait]) { attempt_reconnect }
end
def unbind #:nodoc:
# Allow notifying from which server we were disconnected,
# but only when we didn't trigger disconnecting ourselves.
if @disconnect_cb and connected? and not closing?
@disconnect_cb.call(NATS::ConnectError.new(disconnect_error_string))
end
# If we are closing or shouldn't reconnect, go ahead and disconnect.
process_disconnect and return if (closing? or should_not_reconnect?)
@reconnecting = true if connected?
@connected = false
@pending = @pongs = nil
@buf = nil
cancel_ping_timer
schedule_primary_and_connect
end
def multiple_servers_available?
server_pool && server_pool.size > 1
end
def had_error?
server_pool.first && server_pool.first[:error_received]
end
def should_not_reconnect?
!@options[:reconnect]
end
def cancel_reconnect_timer
if @reconnect_timer
EM.cancel_timer(@reconnect_timer)
@reconnect_timer = nil
end
end
def disconnect_error_string
return "Client disconnected from server on #{@uri}" if @connected
return "Could not connect to server on #{@uri}"
end
def process_disconnect #:nodoc:
# Mute error callback when user has called NATS.close on purpose.
if not closing? and @err_cb
# Always call error callback for compatibility with previous behavior.
err_cb.call(NATS::ConnectError.new(disconnect_error_string))
end
close_cb.call if @close_cb
true # Chaining
ensure
cancel_ping_timer
cancel_reconnect_timer
if (NATS.client == self)
NATS.clear_client
EM.stop if ((connected? || reconnecting?) and closing? and not NATS.reactor_was_running?)
end
@connected = @reconnecting = false
end
def can_reuse_server?(server) #:nodoc:
# If we will retry a number of times to reconnect to a server
# unless we got an error from it already.
reconnecting? && server[:reconnect_attempts] <= @options[:max_reconnect_attempts] && !server[:error_received]
end
def attempt_reconnect #:nodoc:
@reconnect_timer = nil
current = server_pool.first
# Snapshot time when trying to reconnect to server
# in order to back off for subsequent attempts.
current[:last_reconnect_attempt] = MonotonicTime.now
current[:reconnect_attempts] ||= 0
current[:reconnect_attempts] += 1
begin
EM.reconnect(@uri.host, @uri.port, self)
rescue
current[:error_received] = true
@uri = nil
@connected = false
end
end
def send_command(command, priority = false) #:nodoc:
needs_flush = (connected? && @pending.nil?)
@pending ||= []
@pending << command unless priority
@pending.unshift(command) if priority
@pending_size += command.bytesize
EM.next_tick { flush_pending } if needs_flush
flush_pending if (connected? && @pending_size > MAX_PENDING_SIZE)
if (@options[:fast_producer_error] && pending_data_size > FAST_PRODUCER_THRESHOLD)
err_cb.call(NATS::ClientError.new("Fast Producer: #{pending_data_size} bytes outstanding"))
end
true
end
def setup_nkeys_connect
begin
require 'nkeys'
require 'base64'
rescue LoadError
raise(Error, "nkeys is not installed")
end
case
when @nkeys_seed
@user_nkey_cb = proc {
seed = File.read(@nkeys_seed).chomp
kp = NKEYS::from_seed(seed)
# Take a copy since original will be gone with the wipe.
pub_key = kp.public_key.dup
kp.wipe!
pub_key
}
@signature_cb = proc { |nonce|
seed = File.read(@nkeys_seed).chomp
kp = NKEYS::from_seed(seed)
raw_signed = kp.sign(nonce)
kp.wipe!
encoded = Base64.urlsafe_encode64(raw_signed)
encoded.gsub('=', '')
}
when @user_credentials
# When the credentials are within a single decorated file.
@user_jwt_cb = proc {
jwt_start = "BEGIN NATS USER JWT".freeze
found = false
jwt = nil
File.readlines(@user_credentials).each do |line|
case
when found
jwt = line.chomp
break
when line.include?(jwt_start)
found = true
end
end
raise(Error, "No JWT found in #{@user_credentials}") if not found
jwt
}
@signature_cb = proc { |nonce|
seed_start = "BEGIN USER NKEY SEED".freeze
found = false
seed = nil
File.readlines(@user_credentials).each do |line|
case
when found
seed = line.chomp
break
when line.include?(seed_start)
found = true
end
end
raise(Error, "No nkey user seed found in #{@user_credentials}") if not found
kp = NKEYS::from_seed(seed)
raw_signed = kp.sign(nonce)
# seed is a reference so also cleared when doing wipe,
# which can be done since Ruby strings are mutable.
kp.wipe
encoded = Base64.urlsafe_encode64(raw_signed)
# Remove padding
encoded.gsub('=', '')
}
end
end
# Parse out URIs which can now be an array of server choices
# The server pool will contain both explicit and implicit members.
def process_uri_options #:nodoc
@server_pool = []
uri = options[:uris] || options[:servers] || options[:uri]
uri = uri.kind_of?(Array) ? uri : [uri]
uri.each { |u| server_pool << { :uri => u.is_a?(URI) ? u.dup : URI.parse(u) } }
bind_primary
end
# @return [URI] Connected server
def connected_server
connected? ? @uri : nil
end
# Retrieves the list of servers which have been discovered
# via server connect_urls announcements
def discovered_servers
server_pool.select {|s| s[:discovered] }
end
def bind_primary #:nodoc:
first = server_pool.first
@uri = first[:uri]
@uri.user = options[:user] if options[:user]
@uri.password = options[:pass] if options[:pass]
first
end
# We have failed on an attempt at the primary (first) server, rotate and try again
def schedule_primary_and_connect #:nodoc:
# Dump the one we were trying if it wasn't connected
current = server_pool.shift
# In case there was an error from the server we will take it out from rotation
# unless we specify infinite reconnects via setting :max_reconnect_attempts to -1
if current && (options[:max_reconnect_attempts] < 0 || can_reuse_server?(current))
server_pool << current
end
# If we are out of options, go ahead and disconnect then
# handle closing connection to NATS.
process_disconnect and return if server_pool.empty?
# bind new one
next_server = bind_primary
# If the next one was connected and we are trying to reconnect
# set up timer if we tried once already.
if should_delay_connect?(next_server)
schedule_reconnect
else
attempt_reconnect
schedule_primary_and_connect if had_error?
end
end
def inspect #:nodoc:
"<nats client v#{NATS::VERSION}>"
end
class MonotonicTime
class << self
case
when defined?(Process::CLOCK_MONOTONIC)
def now
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
when RUBY_ENGINE == 'jruby'
def now
java.lang.System.nanoTime() / 1_000_000_000.0
end
else
def now
# Fallback to regular time behavior
::Time.now.to_f
end
end
end
end
end
================================================
FILE: lib/nats/ext/bytesize.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
if RUBY_VERSION <= "1.8.6"
class String #:nodoc:
def bytesize; self.size; end
end
end
================================================
FILE: lib/nats/ext/em.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
begin
require 'eventmachine'
rescue LoadError
require 'rubygems'
require 'eventmachine'
end
# Check for get_outbound_data_size support, fake it out if it doesn't exist, e.g. jruby
if !EM::Connection.method_defined? :get_outbound_data_size
class EM::Connection
def get_outbound_data_size; return 0; end
end
end
================================================
FILE: lib/nats/ext/json.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
begin
require 'yajl'
require 'yajl/json_gem'
rescue LoadError
begin
require 'oj'
Oj.mimic_JSON()
rescue LoadError
require 'rubygems'
require 'json'
end
end
================================================
FILE: lib/nats/nuid.rb
================================================
# Copyright 2016-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'securerandom'
module NATS
class NUID
DIGITS = [*'0'..'9', *'A'..'Z', *'a'..'z']
BASE = 62
PREFIX_LENGTH = 12
SEQ_LENGTH = 10
TOTAL_LENGTH = PREFIX_LENGTH + SEQ_LENGTH
MAX_SEQ = BASE**10
MIN_INC = 33
MAX_INC = 333
INC = MAX_INC - MIN_INC
def initialize
@prand = Random.new
@seq = @prand.rand(MAX_SEQ)
@inc = MIN_INC + @prand.rand(INC)
@prefix = ''
randomize_prefix!
end
def next
@seq += @inc
if @seq >= MAX_SEQ
randomize_prefix!
reset_sequential!
end
l = @seq
# Do this inline 10 times to avoid even more extra allocs,
# then use string interpolation of everything which works
# faster for doing concat.
s_10 = DIGITS[l % BASE];
# Ugly, but parallel assignment is slightly faster here...
s_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 = \
(l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]),\
(l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]),\
(l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE])
"#{@prefix}#{s_01}#{s_02}#{s_03}#{s_04}#{s_05}#{s_06}#{s_07}#{s_08}#{s_09}#{s_10}"
end
def randomize_prefix!
@prefix = \
SecureRandom.random_bytes(PREFIX_LENGTH).each_byte
.reduce('') do |prefix, n|
prefix << DIGITS[n % BASE]
end
end
private
def reset_sequential!
@seq = @prand.rand(MAX_SEQ)
@inc = MIN_INC + @prand.rand(INC)
end
end
end
================================================
FILE: lib/nats/server/cluster.rb
================================================
require 'uri'
module NATSD #:nodoc: all
class Server
class << self
attr_reader :opt_routes, :route_auth_required, :route_ssl_required, :reconnect_interval
attr_accessor :num_routes
alias route_auth_required? :route_auth_required
alias route_ssl_required? :route_ssl_required
def connected_routes
@routes ||= []
end
def add_route(route)
connected_routes << route unless route.nil?
end
def remove_route(route)
connected_routes.delete(route) unless route.nil?
end
def route_info_string
@route_info = {
:server_id => Server.id,
:host => @options[:cluster_net] || host,
:port => @options[:cluster_port],
:version => VERSION,
:auth_required => route_auth_required?,
:ssl_required => false, # FIXME!
:max_payload => @max_payload
}
@route_info.to_json
end
def route_key(route_url)
r = URI.parse(route_url)
"#{r.host}:#{r.port}"
end
def route_auth_ok?(user, pass)
user == @options[:cluster_user] && pass == @options[:cluster_pass]
end
def solicit_routes #:nodoc:
@opt_routes = []
NATSD::Server.options[:cluster_routes].each do |r_url|
opt_routes << { :route => r_url, :uri => URI.parse(r_url), :key => route_key(r_url) }
end
try_to_connect_routes
end
def try_to_connect_routes #:nodoc:
opt_routes.each do |route|
# FIXME, Strip auth
debug "Trying to connect to route: #{route[:route]}"
EM.connect(route[:uri].host, route[:uri].port, NATSD::Route, route)
end
end
def broadcast_proto_to_routes(proto)
connected_routes.each { |r| r.queue_data(proto) }
end
def rsid_qsub(rsid)
cid, sid = parse_rsid(rsid)
conn = Server.connections[cid]
sub = conn.subscriptions[sid]
sub if sub.qgroup
rescue
nil
end
def parse_rsid(rsid)
m = RSID.match(rsid)
return [m[1].to_i, m[2]] if m
end
def routed_sid(sub)
"RSID:#{sub.conn.cid}:#{sub.sid}"
end
def route_sub_proto(sub)
return "SUB #{sub.subject} #{routed_sid(sub)}#{CR_LF}" if sub.qgroup.nil?
return "SUB #{sub.subject} #{sub.qgroup} #{routed_sid(sub)}#{CR_LF}"
end
def broadcast_sub_to_routes(sub)
broadcast_proto_to_routes(route_sub_proto(sub))
end
def broadcast_unsub_to_routes(sub)
opt_max_str = " #{sub.max_responses}" unless sub.max_responses.nil?
broadcast_proto_to_routes("UNSUB #{routed_sid(sub)}#{opt_max_str}#{CR_LF}")
end
end
end
end
================================================
FILE: lib/nats/server/connection.rb
================================================
module NATSD #:nodoc: all
module Connection #:nodoc: all
attr_accessor :in_msgs, :out_msgs, :in_bytes, :out_bytes
attr_reader :cid, :closing, :last_activity, :writev_size, :subscriptions
alias :closing? :closing
def flush_data
return if @writev.nil? || closing?
send_data(@writev.join)
@writev, @writev_size = nil, 0
end
def queue_data(data)
EM.next_tick { flush_data } if @writev.nil?
(@writev ||= []) << data
@writev_size += data.bytesize
flush_data if @writev_size > MAX_WRITEV_SIZE
end
def client_info
cur_peername = get_peername
@client_info ||= (cur_peername.nil? ? 'N/A' : Socket.unpack_sockaddr_in(cur_peername))
end
def info
{
:cid => cid,
:ip => client_info[1],
:port => client_info[0],
:subscriptions => @subscriptions.size,
:pending_size => get_outbound_data_size,
:in_msgs => @in_msgs,
:out_msgs => @out_msgs,
:in_bytes => @in_bytes,
:out_bytes => @out_bytes
}
end
def max_connections_exceeded?
return false unless (Server.num_connections > Server.max_connections)
error_close MAX_CONNS_EXCEEDED
debug "Maximum #{Server.max_connections} connections exceeded, c:#{cid} will be closed"
true
end
def post_init
@cid = Server.cid
@subscriptions = {}
@verbose = @pedantic = true # suppressed by most clients, but allows friendly telnet
@in_msgs = @out_msgs = @in_bytes = @out_bytes = 0
@writev_size = 0
@parse_state = AWAITING_CONTROL_LINE
send_info
debug "#{type} connection created", client_info, cid
if Server.ssl_required?
debug "Starting TLS/SSL", client_info, cid
flush_data
@ssl_pending = EM.add_timer(NATSD::Server.ssl_timeout) { connect_ssl_timeout }
start_tls(:verify_peer => true) if Server.ssl_required?
end
@auth_pending = EM.add_timer(NATSD::Server.auth_timeout) { connect_auth_timeout } if Server.auth_required?
@ping_timer = EM.add_periodic_timer(NATSD::Server.ping_interval) { send_ping }
@pings_outstanding = 0
inc_connections
return if max_connections_exceeded?
end
def send_ping
return if @closing
if @pings_outstanding > NATSD::Server.ping_max
error_close UNRESPONSIVE
return
end
queue_data(PING_RESPONSE)
flush_data
@pings_outstanding += 1
end
def connect_auth_timeout
error_close AUTH_REQUIRED
debug "#{type} connection timeout due to lack of auth credentials", cid
end
def connect_ssl_timeout
error_close SSL_REQUIRED
debug "#{type} connection timeout due to lack of TLS/SSL negotiations", cid
end
def receive_data(data)
@buf = @buf ? @buf << data : data
while (@buf && !@closing)
case @parse_state
when AWAITING_CONTROL_LINE
case @buf
when PUB_OP
ctrace('PUB OP', strip_op($&)) if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
@parse_state = AWAITING_MSG_PAYLOAD
@msg_sub, @msg_reply, @msg_size = $1, $3, $4.to_i
if (@msg_size > NATSD::Server.max_payload)
debug_print_msg_too_big(@msg_size)
error_close PAYLOAD_TOO_BIG
end
queue_data(INVALID_SUBJECT) if (@pedantic && !(@msg_sub =~ SUB_NO_WC))
when SUB_OP
ctrace('SUB OP', strip_op($&)) if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
sub, qgroup, sid = $1, $3, $4
return queue_data(INVALID_SUBJECT) if !($1 =~ SUB)
return queue_data(INVALID_SID_TAKEN) if @subscriptions[sid]
sub = Subscriber.new(self, sub, sid, qgroup, 0)
@subscriptions[sid] = sub
Server.subscribe(sub)
queue_data(OK) if @verbose
when UNSUB_OP
ctrace('UNSUB OP', strip_op($&)) if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
sid, sub = $1, @subscriptions[$1]
if sub
# If we have set max_responses, we will unsubscribe once we have received
# the appropriate amount of responses.
sub.max_responses = ($2 && $3) ? $3.to_i : nil
delete_subscriber(sub) unless (sub.max_responses && (sub.num_responses < sub.max_responses))
queue_data(OK) if @verbose
else
queue_data(INVALID_SID_NOEXIST) if @pedantic
end
when PING
ctrace('PING OP') if NATSD::Server.trace_flag?
@buf = $'
queue_data(PONG_RESPONSE)
flush_data
when PONG
ctrace('PONG OP') if NATSD::Server.trace_flag?
@buf = $'
@pings_outstanding -= 1
when CONNECT
ctrace('CONNECT OP', strip_op($&)) if NATSD::Server.trace_flag?
@buf = $'
begin
config = JSON.parse($1)
process_connect_config(config)
rescue => e
queue_data(INVALID_CONFIG)
log_error
end
when INFO_REQ
ctrace('INFO_REQUEST OP') if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
send_info
when UNKNOWN
ctrace('Unknown Op', strip_op($&)) if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
queue_data(UNKNOWN_OP)
when CTRL_C # ctrl+c or ctrl+d for telnet friendly
ctrace('CTRL-C encountered', strip_op($&)) if NATSD::Server.trace_flag?
return close_connection
when CTRL_D # ctrl+d for telnet friendly
ctrace('CTRL-D encountered', strip_op($&)) if NATSD::Server.trace_flag?
return close_connection
else
# If we are here we do not have a complete line yet that we understand.
# If too big, cut the connection off.
if @buf.bytesize > NATSD::Server.max_control_line
debug_print_controlline_too_big(@buf.bytesize)
close_connection
end
return
end
@buf = nil if (@buf && @buf.empty?)
when AWAITING_MSG_PAYLOAD
return unless (@buf.bytesize >= (@msg_size + CR_LF_SIZE))
msg = @buf.slice(0, @msg_size)
ctrace('Processing msg', @msg_sub, @msg_reply, msg) if NATSD::Server.trace_flag?
queue_data(OK) if @verbose
Server.route_to_subscribers(@msg_sub, @msg_reply, msg)
@in_msgs += 1
@in_bytes += @msg_size
@buf = @buf.slice((@msg_size + CR_LF_SIZE), @buf.bytesize)
@msg_sub = @msg_size = @reply = nil
@parse_state = AWAITING_CONTROL_LINE
@buf = nil if (@buf && @buf.empty?)
end
end
end
def send_info
queue_data("INFO #{Server.info_string}#{CR_LF}")
end
# Placeholder
def process_info(info)
end
def auth_ok?(user, pass)
Server.auth_ok?(user, pass)
end
def process_connect_config(config)
@verbose = config['verbose'] unless config['verbose'].nil?
@pedantic = config['pedantic'] unless config['pedantic'].nil?
return queue_data(OK) unless Server.auth_required?
EM.cancel_timer(@auth_pending)
if auth_ok?(config['user'], config['pass'])
queue_data(OK) if @verbose
@auth_pending = nil
else
error_close AUTH_FAILED
debug "Authorization failed for #{type.downcase} connection", cid
end
end
def delete_subscriber(sub)
ctrace('DELSUB OP', sub.subject, sub.qgroup, sub.sid) if NATSD::Server.trace_flag?
Server.unsubscribe(sub, is_route?)
@subscriptions.delete(sub.sid)
end
def error_close(msg)
queue_data(msg)
flush_data
EM.next_tick { close_connection_after_writing }
@closing = true
end
def debug_print_controlline_too_big(line_size)
sizes = "#{pretty_size(line_size)} vs #{pretty_size(NATSD::Server.max_control_line)} max"
debug "Control line size exceeded (#{sizes}), closing connection.."
end
def debug_print_msg_too_big(msg_size)
sizes = "#{pretty_size(msg_size)} vs #{pretty_size(NATSD::Server.max_payload)} max"
debug "Message payload size exceeded (#{sizes}), closing connection"
end
def inc_connections
Server.num_connections += 1
Server.connections[cid] = self
end
def dec_connections
Server.num_connections -= 1
Server.connections.delete(cid)
end
def process_unbind
dec_connections
EM.cancel_timer(@ssl_pending) if @ssl_pending
@ssl_pending = nil
EM.cancel_timer(@auth_pending) if @auth_pending
@auth_pending = nil
EM.cancel_timer(@ping_timer) if @ping_timer
@ping_timer = nil
@subscriptions.each_value { |sub| Server.unsubscribe(sub) }
@closing = true
end
def unbind
debug "Client connection closed", client_info, cid
process_unbind
end
def ssl_handshake_completed
EM.cancel_timer(@ssl_pending)
@ssl_pending = nil
cert = get_peer_cert
debug "#{type} Certificate:", cert ? cert : 'N/A', cid
end
# FIXME! Cert accepted by default
def ssl_verify_peer(cert)
true
end
def ctrace(*args)
trace(args, "c: #{cid}")
end
def strip_op(op='')
op.dup.sub(CR_LF, EMPTY)
end
def is_route?
false
end
def type
'Client'
end
end
end
================================================
FILE: lib/nats/server/connz.rb
================================================
module NATSD #:nodoc: all
class Connz
def call(env)
c_info = Server.dump_connections
qs = env['QUERY_STRING']
if (qs =~ /n=(\d+)/)
sort_key = :pending_size
n = $1.to_i
if (qs =~ /s=(\S+)/)
case $1.downcase
when 'in_msgs'; sort_key = :in_msgs
when 'msgs_from'; sort_key = :in_msgs
when 'out_msgs'; sort_key = :out_msgs
when 'msgs_to'; sort_key = :out_msgs
when 'in_bytes'; sort_key = :in_bytes
when 'bytes_from'; sort_key = :in_bytes
when 'out_bytes'; sort_key = :out_bytes
when 'bytes_to'; sort_key = :out_bytes
when 'subs'; sort_key = :subscriptions
when 'subscriptions'; sort_key = :subscriptions
end
end
conns = c_info[:connections]
c_info[:connections] = conns.sort { |a,b| b[sort_key] <=> a[sort_key] } [0, n]
end
connz_json = JSON.pretty_generate(c_info) + "\n"
hdrs = RACK_JSON_HDR.dup
hdrs['Content-Length'] = connz_json.bytesize.to_s
[200, hdrs, connz_json]
end
end
class Server
class << self
def dump_connections
conns, total = [], 0
ObjectSpace.each_object(NATSD::Connection) do |c|
next if c.closing?
total += c.info[:pending_size]
conns << c.info
end
{
:pending_size => total,
:num_connections => conns.size,
:connections => conns
}
end
end
end
end
================================================
FILE: lib/nats/server/const.rb
================================================
module NATSD #:nodoc:
VERSION = '0.5.1'
APP_NAME = 'nats-server'
DEFAULT_PORT = 4222
DEFAULT_HOST = '0.0.0.0'
# Parser
AWAITING_CONTROL_LINE = 1
AWAITING_MSG_PAYLOAD = 2
# Ops - See protocol.txt for more info
INFO = /\AINFO\s*([^\r\n]*)\r\n/i
PUB_OP = /\APUB\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i
MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i
SUB_OP = /\ASUB\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?([^\s]+)\r\n/i
UNSUB_OP = /\AUNSUB\s+([^\s]+)\s*(\s+(\d+))?\r\n/i
PING = /\APING\s*\r\n/i
PONG = /\APONG\s*\r\n/i
INFO_REQ = /\AINFO_REQ\s*\r\n/i
CONNECT = /\ACONNECT\s+([^\r\n]+)\r\n/i
UNKNOWN = /\A(.*)\r\n/
CTRL_C = /\006/
CTRL_D = /\004/
ERR_RESP = /\A-ERR\s+('.+')?\r\n/i
OK_RESP = /\A\+OK\s*\r\n/i #:nodoc:
# RESPONSES
CR_LF = "\r\n".freeze
CR_LF_SIZE = CR_LF.bytesize
EMPTY = ''.freeze
OK = "+OK#{CR_LF}".freeze
PING_RESPONSE = "PING#{CR_LF}".freeze
PONG_RESPONSE = "PONG#{CR_LF}".freeze
INFO_RESPONSE = "#{CR_LF}".freeze
# ERR responses
PAYLOAD_TOO_BIG = "-ERR 'Payload size exceeded'#{CR_LF}".freeze
PROTOCOL_OP_TOO_BIG = "-ERR 'Protocol Operation size exceeded'#{CR_LF}".freeze
INVALID_SUBJECT = "-ERR 'Invalid Subject'#{CR_LF}".freeze
INVALID_SID_TAKEN = "-ERR 'Invalid Subject Identifier (sid), already taken'#{CR_LF}".freeze
INVALID_SID_NOEXIST = "-ERR 'Invalid Subject-Identifier (sid), no subscriber registered'#{CR_LF}".freeze
INVALID_CONFIG = "-ERR 'Invalid config, valid JSON required for connection configuration'#{CR_LF}".freeze
AUTH_REQUIRED = "-ERR 'Authorization is required'#{CR_LF}".freeze
AUTH_FAILED = "-ERR 'Authorization failed'#{CR_LF}".freeze
SSL_REQUIRED = "-ERR 'TSL/SSL is required'#{CR_LF}".freeze
SSL_FAILED = "-ERR 'TLS/SSL failed'#{CR_LF}".freeze
UNKNOWN_OP = "-ERR 'Unknown Protocol Operation'#{CR_LF}".freeze
SLOW_CONSUMER = "-ERR 'Slow consumer detected, connection dropped'#{CR_LF}".freeze
UNRESPONSIVE = "-ERR 'Unresponsive client detected, connection dropped'#{CR_LF}".freeze
MAX_CONNS_EXCEEDED = "-ERR 'Maximum client connections exceeded, connection dropped'#{CR_LF}".freeze
# Pedantic Mode
SUB = /^([^\.\*>\s]+|>$|\*)(\.([^\.\*>\s]+|>$|\*))*$/
SUB_NO_WC = /^([^\.\*>\s]+)(\.([^\.\*>\s]+))*$/
# Router Subscription Identifiers
RSID = /RSID:(\d+):(\S+)/
# Some sane default thresholds
# 1k should be plenty since payloads sans connect string are separate
MAX_CONTROL_LINE_SIZE = 1024
# Should be using something different if > 1MB payload
MAX_PAYLOAD_SIZE = (1024*1024)
# Maximum outbound size per client
MAX_PENDING_SIZE = (10*1024*1024)
# Maximum pending bucket size
MAX_WRITEV_SIZE = (64*1024)
# Maximum connections default
DEFAULT_MAX_CONNECTIONS = (64*1024)
# TLS/SSL wait time
SSL_TIMEOUT = 0.5
# Authorization wait time
AUTH_TIMEOUT = SSL_TIMEOUT + 0.5
# Ping intervals
DEFAULT_PING_INTERVAL = 120
DEFAULT_PING_MAX = 2
# Route Reconnect
DEFAULT_ROUTE_RECONNECT_INTERVAL = 1.0
# HTTP
RACK_JSON_HDR = { 'Content-Type' => 'application/json' }
RACK_TEXT_HDR = { 'Content-Type' => 'text/plain' }
end
================================================
FILE: lib/nats/server/options.rb
================================================
require 'optparse'
require 'yaml'
module NATSD
class Server
class << self
def parser
@parser ||= OptionParser.new do |opts|
opts.banner = "Usage: nats-server [options]"
opts.separator ""
opts.separator "Server options:"
opts.on("-a", "--addr HOST", "Bind to HOST address " +
"(default: #{DEFAULT_HOST})") { |host| @options[:addr] = host }
opts.on("-p", "--port PORT", "Use PORT (default: #{DEFAULT_PORT})") { |port| @options[:port] = port.to_i }
opts.on("-d", "--daemonize", "Run daemonized in the background") { @options[:daemonize] = true }
opts.on("-P", "--pid FILE", "File to store PID") { |file| @options[:pid_file] = file }
opts.on("-m", "--http_port PORT", "Use HTTP PORT ") { |port| @options[:http_port] = port.to_i }
opts.on("-r", "--cluster_port PORT", "Use Cluster PORT ") { |port| @options[:cluster_port] = port.to_i }
opts.on("-c", "--config FILE", "Configuration File") { |file| @options[:config_file] = file }
opts.separator ""
opts.separator "Logging options:"
opts.on("-l", "--log FILE", "File to redirect log output") { |file| @options[:log_file] = file }
opts.on("-T", "--logtime", "Timestamp log entries (default: false)") { @options[:log_time] = true }
opts.on("-S", "--syslog IDENT", "Enable Syslog output") { |ident| @options[:syslog] = ident }
opts.on("-D", "--debug", "Enable debugging output") { @options[:debug] = true }
opts.on("-V", "--trace", "Trace the raw protocol") { @options[:trace] = true }
opts.separator ""
opts.separator "Authorization options:"
opts.on("--user user", "User required for connections") { |user| @options[:user] = user }
opts.on("--pass password", "Password required for connections") { |pass| @options[:pass] = pass }
opts.separator ""
opts.on("--ssl", "Enable SSL") { |ssl| @options[:ssl] = true }
opts.separator ""
opts.separator "Advanced IO options:"
opts.on("--no_epoll", "Disable epoll (Linux)") { @options[:noepoll] = true }
opts.on("--no_kqueue", "Disable kqueue (MacOSX and BSD)") { @options[:nokqueue] = true }
opts.separator ""
opts.separator "Common options:"
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
opts.on_tail('-v', '--version', "Show version") { puts NATSD::Server.version; exit }
end
end
def read_config_file
return unless config_file = @options[:config_file]
config = File.open(config_file) { |f| YAML.load(f) }
# Command lines args, parsed first, will override these.
@options[:port] = config['port'] if @options[:port].nil?
@options[:addr] = config['net'] if @options[:addr].nil?
if auth = config['authorization']
@options[:user] = auth['user'] if @options[:user].nil?
@options[:pass] = auth['password'] if @options[:pass].nil?
@options[:pass] = auth['pass'] if @options[:pass].nil?
@options[:token] = auth['token'] if @options[:token].nil?
@options[:auth_timeout] = auth['timeout'] if @options[:auth_timeout].nil?
# Multiple Users setup
@options[:users] = symbolize_users(auth['users']) || []
end
# TLS/SSL
@options[:ssl] = config['ssl'] if @options[:ssl].nil?
@options[:pid_file] = config['pid_file'] if @options[:pid_file].nil?
@options[:log_file] = config['log_file'] if @options[:log_file].nil?
@options[:log_time] = config['logtime'] if @options[:log_time].nil?
@options[:syslog] = config['syslog'] if @options[:syslog].nil?
@options[:debug] = config['debug'] if @options[:debug].nil?
@options[:trace] = config['trace'] if @options[:trace].nil?
# these just override if present
@options[:max_control_line] = config['max_control_line'] if config['max_control_line']
@options[:max_payload] = config['max_payload'] if config['max_payload']
@options[:max_pending] = config['max_pending'] if config['max_pending']
@options[:max_connections] = config['max_connections'] if config['max_connections']
# just set
@options[:noepoll] = config['no_epoll'] if config['no_epoll']
@options[:nokqueue] = config['no_kqueue'] if config['no_kqueue']
if http = config['http']
if @options[:http_net].nil?
@options[:http_net] = http['net'] || @options[:addr]
end
@options[:http_port] = http['port'] if @options[:http_port].nil?
@options[:http_user] = http['user'] if @options[:http_user].nil?
@options[:http_password] = http['password'] if @options[:http_password].nil?
end
if ping = config['ping']
@options[:ping_interval] = ping['interval'] if @options[:ping_interval].nil?
@options[:ping_max] = ping['max_outstanding'] if @options[:ping_max].nil?
end
if cluster = config['cluster']
@options[:cluster_port] = cluster['port'] if @options[:cluster_port].nil?
if auth = cluster['authorization']
@options[:cluster_user] = auth['user'] if @options[:cluster_user].nil?
@options[:cluster_pass] = auth['password'] if @options[:cluster_pass].nil?
@options[:cluster_pass] = auth['pass'] if @options[:cluster_pass].nil?
@options[:cluster_token] = auth['token'] if @options[:cluster_token].nil?
@options[:cluster_auth_timeout] = auth['timeout'] if @options[:cluster_auth_timeout].nil?
@route_auth_required = true
end
if routes = cluster['routes']
@options[:cluster_routes] = routes if @options[:cluster_routes].nil?
end
end
rescue => e
log "Could not read configuration file: #{e}"
exit 1
end
def setup_logs
return unless @options[:log_file]
$stdout.reopen(@options[:log_file], 'a')
$stdout.sync = true
$stderr.reopen($stdout)
end
def open_syslog
return unless @options[:syslog]
Syslog.open("#{@options[:syslog]}", Syslog::LOG_PID, Syslog::LOG_USER) unless Syslog.opened?
end
def close_syslog
Syslog.close if @options[:syslog]
end
def symbolize_users(users)
return nil unless users
auth_users = []
users.each do |u|
auth_users << { :user => u['user'], :pass => u['pass'] || u['password'] }
end
auth_users
end
def finalize_options
# Addr/Port
@options[:port] ||= DEFAULT_PORT
@options[:addr] ||= DEFAULT_HOST
# Max Connections
@options[:max_connections] ||= DEFAULT_MAX_CONNECTIONS
@max_connections = @options[:max_connections]
# Debug and Tracing
@debug_flag = @options[:debug]
@trace_flag = @options[:trace]
# Log timestamps
@log_time = @options[:log_time]
debug @options # Block pass?
debug "DEBUG is on"
trace "TRACE is on"
# Syslog
@syslog = @options[:syslog]
# Authorization
# Multi-user setup for auth
if @options[:user]
# Multiple Users setup
@options[:users] ||= []
@options[:users].unshift({:user => @options[:user], :pass => @options[:pass]}) if @options[:user]
elsif @options[:users]
first = @options[:users].first
@options[:user], @options[:pass] = first[:user], first[:pass]
end
@auth_required = (not @options[:user].nil?)
@ssl_required = @options[:ssl]
# Pings
@options[:ping_interval] ||= DEFAULT_PING_INTERVAL
@ping_interval = @options[:ping_interval]
@options[:ping_max] ||= DEFAULT_PING_MAX
@ping_max = @options[:ping_max]
# Thresholds
@options[:max_control_line] ||= MAX_CONTROL_LINE_SIZE
@max_control_line = @options[:max_control_line]
@options[:max_payload] ||= MAX_PAYLOAD_SIZE
@max_payload = @options[:max_payload]
@options[:max_pending] ||= MAX_PENDING_SIZE
@max_pending = @options[:max_pending]
@options[:auth_timeout] ||= AUTH_TIMEOUT
@auth_timeout = @options[:auth_timeout]
@options[:ssl_timeout] ||= SSL_TIMEOUT
@ssl_timeout = @options[:ssl_timeout]
end
end
end
end
================================================
FILE: lib/nats/server/route.rb
================================================
module NATSD #:nodoc: all
# Need to make this a class with EM > 1.0
class Route < EventMachine::Connection #:nodoc:
include Connection
attr_reader :rid, :remote_rid, :closing, :r_obj, :reconnecting
alias :peer_info :client_info
alias :reconnecting? :reconnecting
def initialize(route=nil)
@r_obj = route
end
def solicited?
r_obj != nil
end
def connection_completed
debug "Route connected", rid
return unless reconnecting?
# Kill reconnect if we got here from there
cancel_reconnect
@buf, @closing = nil, false
post_init
end
def post_init
@rid = Server.rid
@subscriptions = {}
@in_msgs = @out_msgs = @in_bytes = @out_bytes = 0
@writev_size = 0
@parse_state = AWAITING_CONTROL_LINE
# Queue up auth if needed and we solicited the connection
debug "Route connection created", peer_info, rid
# queue up auth if needed and we solicited the connection
if solicited?
debug "Route sent authorization", rid
send_auth
else
# FIXME, separate variables for timeout?
@auth_pending = EM.add_timer(NATSD::Server.auth_timeout) { connect_auth_timeout } if Server.route_auth_required?
end
send_info
@ping_timer = EM.add_periodic_timer(NATSD::Server.ping_interval) { send_ping }
@pings_outstanding = 0
inc_connections
send_local_subs_to_route
end
# TODO: Make sure max_requested is also propogated on reconnect
def send_local_subs_to_route
ObjectSpace.each_object(NATSD::Connection) do |c|
next if c.closing? || c.type != 'Client'
c.subscriptions.each_value do |sub|
queue_data(NATSD::Server.route_sub_proto(sub))
end
end
end
def process_connect_route_config(config)
@verbose = config['verbose'] unless config['verbose'].nil?
@pedantic = config['pedantic'] unless config['pedantic'].nil?
return queue_data(OK) unless Server.route_auth_required?
EM.cancel_timer(@auth_pending)
if auth_ok?(config['user'], config['pass'])
debug "Route received proper credentials", rid
queue_data(OK) if @verbose
@auth_pending = nil
else
error_close AUTH_FAILED
debug "Authorization failed for #{type.downcase} connection", rid
end
end
def connect_auth_timeout
error_close AUTH_REQUIRED
debug "#{type} connection timeout due to lack of auth credentials", rid
end
def receive_data(data)
@buf = @buf ? @buf << data : data
return close_connection if @buf =~ /(\006|\004)/ # ctrl+c or ctrl+d for telnet friendly
while (@buf && !@closing)
case @parse_state
when AWAITING_CONTROL_LINE
case @buf
when MSG
ctrace('MSG OP', strip_op($&)) if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
@parse_state = AWAITING_MSG_PAYLOAD
@msg_sub, @msg_sid, @msg_reply, @msg_size = $1, $2, $4, $5.to_i
if (@msg_size > NATSD::Server.max_payload)
debug_print_msg_too_big(@msg_size)
error_close PAYLOAD_TOO_BIG
end
queue_data(INVALID_SUBJECT) if (@pedantic && !(@msg_sub =~ SUB_NO_WC))
when SUB_OP
ctrace('SUB OP', strip_op($&)) if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
sub, qgroup, sid = $1, $3, $4
return queue_data(INVALID_SUBJECT) if !($1 =~ SUB)
return queue_data(INVALID_SID_TAKEN) if @subscriptions[sid]
sub = Subscriber.new(self, sub, sid, qgroup, 0)
@subscriptions[sid] = sub
Server.subscribe(sub, is_route?)
queue_data(OK) if @verbose
when UNSUB_OP
ctrace('UNSUB OP', strip_op($&)) if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
sid, sub = $1, @subscriptions[$1]
if sub
# If we have set max_responses, we will unsubscribe once we have received
# the appropriate amount of responses.
sub.max_responses = ($2 && $3) ? $3.to_i : nil
delete_subscriber(sub) unless (sub.max_responses && (sub.num_responses < sub.max_responses))
queue_data(OK) if @verbose
else
queue_data(INVALID_SID_NOEXIST) if @pedantic
end
when PING
ctrace('PING OP') if NATSD::Server.trace_flag?
@buf = $'
queue_data(PONG_RESPONSE)
flush_data
when PONG
ctrace('PONG OP') if NATSD::Server.trace_flag?
@buf = $'
@pings_outstanding -= 1
when CONNECT
ctrace('CONNECT OP', strip_op($&)) if NATSD::Server.trace_flag?
@buf = $'
begin
config = JSON.parse($1)
process_connect_route_config(config)
rescue => e
queue_data(INVALID_CONFIG)
log_error
end
when INFO_REQ
ctrace('INFO_REQUEST OP') if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
send_info
when INFO
ctrace('INFO OP', strip_op($&)) if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
process_info($1)
when ERR_RESP
ctrace('-ERR', $1) if NATSD::Server.trace_flag?
close_connection
exit
when OK_RESP
ctrace('+OK') if NATSD::Server.trace_flag?
@buf = $'
when UNKNOWN
ctrace('Unknown Op', strip_op($&)) if NATSD::Server.trace_flag?
return connect_auth_timeout if @auth_pending
@buf = $'
queue_data(UNKNOWN_OP)
else
# If we are here we do not have a complete line yet that we understand.
# If too big, cut the connection off.
if @buf.bytesize > NATSD::Server.max_control_line
debug_print_controlline_too_big(@buf.bytesize)
close_connection
end
return
end
@buf = nil if (@buf && @buf.empty?)
when AWAITING_MSG_PAYLOAD
return unless (@buf.bytesize >= (@msg_size + CR_LF_SIZE))
msg = @buf.slice(0, @msg_size)
ctrace('Processing routed msg', @msg_sub, @msg_reply, msg) if NATSD::Server.trace_flag?
queue_data(OK) if @verbose
# We deliver normal subscriptions like a client publish, which
# eliminates the duplicate traversal over the route. However,
# qgroups are sent individually per group for only the route
# with the intended subscriber, since route interest is L2
# semantics, we deliver those direct.
if (sub = Server.rsid_qsub(@msg_sid))
# Allows nil reply to not have extra space
reply = @msg_reply + ' ' if @msg_reply
Server.deliver_to_subscriber(sub, @msg_sub, reply, msg)
else
Server.route_to_subscribers(@msg_sub, @msg_reply, msg, is_route?)
end
@in_msgs += 1
@in_bytes += @msg_size
@buf = @buf.slice((@msg_size + CR_LF_SIZE), @buf.bytesize)
@msg_sub = @msg_size = @reply = nil
@parse_state = AWAITING_CONTROL_LINE
@buf = nil if (@buf && @buf.empty?)
end
end
end
def send_auth
return unless r_obj[:uri].user
cs = { :user => r_obj[:uri].user, :pass => r_obj[:uri].password }
queue_data("CONNECT #{cs.to_json}#{CR_LF}")
end
def send_info
queue_data("INFO #{Server.route_info_string}#{CR_LF}")
end
def process_info(info_json)
info = JSON.parse(info_json)
@remote_rid = info['server_id'] unless info['server_id'].nil?
super(info_json)
end
def auth_ok?(user, pass)
Server.route_auth_ok?(user, pass)
end
def inc_connections
Server.num_routes += 1
Server.add_route(self)
end
def dec_connections
Server.num_routes -= 1
Server.remove_route(self)
end
def try_reconnect
debug "Trying to reconnect route", peer_info, rid
EM.reconnect(r_obj[:uri].host, r_obj[:uri].port, self)
end
def cancel_reconnect
EM.cancel_timer(@reconnect_timer) if @reconnect_timer
@reconnect_timer = nil
@reconnecting = false
end
def unbind
return if reconnecting?
debug "Route connection closed", peer_info, rid
process_unbind
if solicited?
@reconnecting = true
@reconnect_timer = EM.add_periodic_timer(NATSD::DEFAULT_ROUTE_RECONNECT_INTERVAL) { try_reconnect }
end
end
def ctrace(*args)
trace(args, "r: #{rid}")
end
def is_route?
true
end
def type
'Route'
end
end
end
================================================
FILE: lib/nats/server/server.rb
================================================
require 'set'
module NATSD #:nodoc: all
# Subscriber
Subscriber = Struct.new(:conn, :subject, :sid, :qgroup, :num_responses, :max_responses)
class Server
class << self
attr_reader :id, :info, :log_time, :auth_required, :ssl_required, :debug_flag, :trace_flag, :syslog, :options
attr_reader :max_payload, :max_pending, :max_control_line, :auth_timeout, :ssl_timeout, :ping_interval, :ping_max
attr_accessor :varz, :healthz, :connections, :max_connections, :num_connections, :in_msgs, :out_msgs, :in_bytes, :out_bytes
alias auth_required? :auth_required
alias ssl_required? :ssl_required
alias debug_flag? :debug_flag
alias trace_flag? :trace_flag
def version; "nats-server version #{NATSD::VERSION}" end
def host; @options[:addr] end
def port; @options[:port] end
def pid_file; @options[:pid_file] end
def process_options(argv=[])
@options = {}
# Allow command line to override config file, so do them first.
parser.parse!(argv)
read_config_file if @options[:config_file]
finalize_options
rescue OptionParser::InvalidOption => e
log_error "Error parsing options: #{e}"
exit(1)
end
def setup(argv)
process_options(argv)
@id, @cid, @rid = fast_uuid, 1, 1
@sublist = Sublist.new
@connections = {}
@num_connections = 0
@in_msgs = @out_msgs = 0
@in_bytes = @out_bytes = 0
@num_routes = 0
@info = {
:server_id => Server.id,
:host => host,
:port => port,
:version => VERSION,
:auth_required => auth_required?,
:ssl_required => ssl_required?,
:max_payload => @max_payload
}
# Check for daemon flag
if @options[:daemonize]
require 'rubygems'
require 'daemons'
require 'tmpdir'
unless @options[:log_file]
# These log messages visible to controlling TTY
log "Starting #{NATSD::APP_NAME} version #{NATSD::VERSION} on port #{NATSD::Server.port}"
log "Starting http monitor on port #{@options[:http_port]}" if @options[:http_port]
log "Starting routing on port #{@options[:cluster_port]}" if @options[:cluster_port]
log "Switching to daemon mode"
end
opts = {
:app_name => APP_NAME,
:mode => :exec,
:dir_mode => :normal,
:dir => Dir.tmpdir
}
Daemons.daemonize(opts)
FileUtils.rm_f("#{Dir.tmpdir}/#{APP_NAME}.pid")
end
setup_logs
open_syslog
# Setup optimized select versions
EM.epoll unless @options[:noepoll]
EM.kqueue unless @options[:nokqueue]
# Write pid file if requested.
File.open(@options[:pid_file], 'w') { |f| f.puts "#{Process.pid}" } if @options[:pid_file]
end
def subscribe(sub, is_route=false)
@sublist.insert(sub.subject, sub)
broadcast_sub_to_routes(sub) unless is_route
end
def unsubscribe(sub, is_route=false)
@sublist.remove(sub.subject, sub)
broadcast_unsub_to_routes(sub) unless is_route
end
def deliver_to_subscriber(sub, subject, reply, msg)
conn = sub.conn
# Accounting
@out_msgs += 1
conn.out_msgs += 1
unless msg.nil?
mbs = msg.bytesize
@out_bytes += mbs
conn.out_bytes += mbs
end
conn.queue_data("MSG #{subject} #{sub.sid} #{reply}#{msg.bytesize}#{CR_LF}#{msg}#{CR_LF}")
# Account for these response and check for auto-unsubscribe (pruning interest graph)
sub.num_responses += 1
conn.delete_subscriber(sub) if (sub.max_responses && sub.num_responses >= sub.max_responses)
# Check the outbound queue here and react if need be..
if (conn.get_outbound_data_size + conn.writev_size) > NATSD::Server.max_pending
conn.error_close SLOW_CONSUMER
maxp = pretty_size(NATSD::Server.max_pending)
log "Slow consumer dropped, exceeded #{maxp} pending", conn.client_info
end
end
def route_to_subscribers(subject, reply, msg, is_route=false)
qsubs = nil
# Allows nil reply to not have extra space
reply = reply + ' ' if reply
# Accounting
@in_msgs += 1
@in_bytes += msg.bytesize unless msg.nil?
# Routes
routes = nil
@sublist.match(subject).each do |sub|
# Skip anyone in the closing state
next if sub.conn.closing
# Skip all routes if sourced from another route (1-hop semantics)
next if (is_route && sub.conn.is_route?)
if sub[:qgroup].nil?
if sub.conn.is_route?
# Only send messages once over a given route
routes ||= Set.new
deliver_to_subscriber(sub, subject, reply, msg) unless routes.include?(sub.conn.remote_rid)
routes << sub.conn.remote_rid
else
deliver_to_subscriber(sub, subject, reply, msg)
end
elsif !is_route
if NATSD::Server.trace_flag?
trace('Matched queue subscriber', sub[:subject], sub[:qgroup], sub[:sid], sub.conn.client_info)
end
# Queue this for post processing
qsubs ||= Hash.new
qsubs[sub[:qgroup]] ||= []
qsubs[sub[:qgroup]] << sub
end
end
return unless qsubs
qsubs.each_value do |subs|
# Randomly pick a subscriber from the group
sub = subs[rand*subs.size]
if NATSD::Server.trace_flag?
trace('Selected queue subscriber', sub[:subject], sub[:qgroup], sub[:sid], sub.conn.client_info)
end
deliver_to_subscriber(sub, subject, reply, msg)
end
end
def auth_ok?(user, pass)
@options[:users].each { |u| return true if (user == u[:user] && pass == u[:pass]) }
false
end
def cid
@cid += 1
end
def rid
@rid += 1
end
def info_string
@info.to_json
end
# Monitoring
def start_http_server
return unless port = @options[:http_port]
require 'thin'
log "Starting http monitor on port #{port}"
@healthz = "ok\n"
@varz = {
:start => Time.now,
:options => @options,
:cores => num_cpu_cores
}
http_server = Thin::Server.new(@options[:http_net], port, :signals => false) do
Thin::Logging.silent = true
if NATSD::Server.options[:http_user]
auth = [NATSD::Server.options[:http_user], NATSD::Server.options[:http_password]]
use Rack::Auth::Basic do |username, password|
[username, password] == auth
end
end
map '/healthz' do
run lambda { |env| [200, RACK_TEXT_HDR, NATSD::Server.healthz] }
end
map '/varz' do
run Varz.new
end
map '/connz' do
run Connz.new
end
end
http_server.start!
end
end
end
end
================================================
FILE: lib/nats/server/sublist.rb
================================================
#--
#
# Sublist implementation for a publish-subscribe system.
# This container class holds subscriptions and matches
# candidate subjects to those subscriptions.
# Certain wildcards are supported for subscriptions.
# '*' will match any given token at any level.
# '>' will match all subsequent tokens.
#--
# See included test for example usage:
##
class Sublist #:nodoc:
PWC = '*'.freeze
FWC = '>'.freeze
CACHE_SIZE = 4096
attr_reader :count
SublistNode = Struct.new(:leaf_nodes, :next_level)
SublistLevel = Struct.new(:nodes, :pwc, :fwc)
EMPTY_LEVEL = SublistLevel.new({})
def initialize(options = {})
@count = 0
@results = []
@root = SublistLevel.new({})
@cache = {}
end
# Ruby is a great language to make selective trade offs of space versus time.
# We do that here with a low tech front end cache. The cache holds results
# until it is exhausted or if the instance inserts or removes a subscription.
# The assumption is that the cache is best suited for high speed matching,
# and that once it is cleared out it will naturally fill with the high speed
# matches. This can obviously be improved with a smarter LRU structure that
# does not need to completely go away when a remove happens..
#
# front end caching is on by default, but we can turn it off here if needed
def disable_cache; @cache = nil; end
def enable_cache; @cache ||= {}; end
def clear_cache; @cache = {} if @cache; end
# Random removal
def prune_cache
return unless @cache
keys = @cache.keys
@cache.delete(keys[rand(keys.size)])
end
# Insert a subscriber into the sublist for the given subject.
def insert(subject, subscriber)
# TODO - validate subject as correct.
level, tokens = @root, subject.split('.')
for token in tokens
# This is slightly slower than direct if statements, but looks cleaner.
case token
when FWC then node = (level.fwc || (level.fwc = SublistNode.new([])))
when PWC then node = (level.pwc || (level.pwc = SublistNode.new([])))
else node = ((level.nodes[token]) || (level.nodes[token] = SublistNode.new([])))
end
level = (node.next_level || (node.next_level = SublistLevel.new({})))
end
node.leaf_nodes.push(subscriber)
@count += 1
clear_cache # Clear the cache
node.next_level = nil if node.next_level == EMPTY_LEVEL
end
# Remove a given subscriber from the sublist for the given subject.
def remove(subject, subscriber)
return unless subject && subscriber
remove_level(@root, subject.split('.'), subscriber)
end
# Match a subject to all subscribers, return the array of matches.
def match(subject)
return @cache[subject] if (@cache && @cache[subject])
tokens = subject.split('.')
@results.clear
matchAll(@root, tokens)
# FIXME: This is too low tech, will revisit when needed.
if @cache
prune_cache if @cache.size > CACHE_SIZE
@cache[subject] = Array.new(@results).freeze # Avoid tampering of copy
end
@results
end
private
def matchAll(level, tokens)
node, pwc = nil, nil # Define for scope
i, ts = 0, tokens.size
while (i < ts) do
return if level == nil
# Handle a full wildcard here by adding all of the subscribers.
@results.concat(level.fwc.leaf_nodes) if level.fwc
# Handle an internal partial wildcard by branching recursively
lpwc = level.pwc
matchAll(lpwc.next_level, tokens[i+1, ts]) if lpwc
node, pwc = level.nodes[tokens[i]], lpwc
#level = node.next_level if node
level = node ? node.next_level : nil
i += 1
end
@results.concat(pwc.leaf_nodes) if pwc
@results.concat(node.leaf_nodes) if node
end
def prune_level(level, node, token)
# Prune here if needed.
return unless level && node
return unless node.leaf_nodes.empty? && (!node.next_level || node.next_level == EMPTY_LEVEL)
if node == level.fwc
level.fwc = nil
elsif node == level.pwc
level.pwc = nil
else
level.nodes.delete(token)
end
end
def remove_level(level, tokens, subscriber)
return unless level
token = tokens.shift
case token
when FWC then node = level.fwc
when PWC then node = level.pwc
else node = level.nodes[token]
end
return unless node
# This could be expensive if a large number of subscribers exist.
if tokens.empty?
if (node.leaf_nodes && node.leaf_nodes.delete(subscriber))
@count -= 1
prune_level(level, node, token)
clear_cache # Clear the cache
end
else
remove_level(node.next_level, tokens, subscriber)
prune_level(level, node, token)
end
end
################################################
# Used for tests on pruning subscription nodes.
################################################
def node_count_level(level, nc)
return 0 unless level
nc += 1 if level.fwc
nc += node_count_level(level.pwc.next_level, nc+1) if level.pwc
level.nodes.each_value do |node|
nc += node_count_level(node.next_level, nc)
end
nc += level.nodes.length
end
def node_count
node_count_level(@root, 0)
end
end
================================================
FILE: lib/nats/server/util.rb
================================================
require 'pp'
def fast_uuid #:nodoc:
v = [rand(0x0010000),rand(0x0010000),rand(0x0010000),
rand(0x0010000),rand(0x0010000),rand(0x1000000)]
"%04x%04x%04x%04x%04x%06x" % v
end
def syslog(args, priority) #:nodoc:
Syslog::log(priority, '%s', PP::pp(args.compact, '', 120))
end
def log(*args) #:nodoc:
return syslog(args, Syslog::LOG_NOTICE) if NATSD::Server.syslog
args.unshift(Time.now) if NATSD::Server.log_time
PP::pp(args.compact, $stdout, 120)
end
def debug(*args) #:nodoc:
return unless NATSD::Server.debug_flag?
return syslog(args, Syslog::LOG_INFO) if NATSD::Server.syslog
log(*args)
end
def trace(*args) #:nodoc:
return unless NATSD::Server.trace_flag?
return syslog(args, Syslog::LOG_DEBUG) if NATSD::Server.syslog
log(*args)
end
def log_error(e=$!) #:nodoc:
debug e, e.backtrace
end
def uptime_string(delta)
num_seconds = delta.to_i
days = num_seconds / (60 * 60 * 24);
num_seconds -= days * (60 * 60 * 24);
hours = num_seconds / (60 * 60);
num_seconds -= hours * (60 * 60);
minutes = num_seconds / 60;
num_seconds -= minutes * 60;
"#{days}d:#{hours}h:#{minutes}m:#{num_seconds}s"
end
def pretty_size(size, prec=1)
return 'NA' unless size
return "#{size}B" if size < 1024
return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
end
def num_cpu_cores
if RUBY_PLATFORM =~ /linux/
return `cat /proc/cpuinfo | grep processor | wc -l`.to_i
elsif RUBY_PLATFORM =~ /darwin/
`sysctl -n hw.ncpu`.strip.to_i
elsif RUBY_PLATFORM =~ /freebsd|netbsd/
`sysctl hw.ncpu`.strip.to_i
else
return 1
end
end
def shutdown #:nodoc:
puts
log 'Server exiting..'
NATSD::Server.close_syslog
EM.stop
if NATSD::Server.pid_file
FileUtils.rm(NATSD::Server.pid_file) if File.exists? NATSD::Server.pid_file
end
exit
end
['TERM','INT'].each { |s| trap(s) { shutdown } }
# FIXME - Should probably be smarter when lots of connections
def dump_connection_state
log "Dumping connection state on SIG_USR2"
ObjectSpace.each_object(NATSD::Connection) do |c|
log c.info unless c.closing?
end
log 'Connection Dump Complete'
end
trap('USR2') { dump_connection_state }
================================================
FILE: lib/nats/server/varz.rb
================================================
module NATSD #:nodoc: all
class Varz
def call(env)
varz_json = JSON.pretty_generate(Server.update_varz) + "\n"
hdrs = RACK_JSON_HDR.dup
hdrs['Content-Length'] = varz_json.bytesize.to_s
[200, hdrs, varz_json]
end
end
class Server
class << self
def update_varz
# Snapshot uptime
@varz[:uptime] = uptime_string(Time.now - @varz[:start])
# Grab current cpu and memory usage.
rss, pcpu = `ps -o rss=,pcpu= -p #{Process.pid}`.split
@varz[:mem] = rss.to_i
@varz[:cpu] = pcpu.to_f
@varz[:connections] = num_connections
@varz[:in_msgs] = in_msgs
@varz[:out_msgs] = out_msgs
@varz[:in_bytes] = in_bytes
@varz[:out_bytes] = out_bytes
@varz[:routes] = num_routes
@last_varz_update = Time.now.to_f
varz
end
end
end
end
================================================
FILE: lib/nats/server.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'socket'
require 'fileutils'
require 'pp'
require 'syslog'
ep = File.expand_path(File.dirname(__FILE__))
require "#{ep}/ext/em"
require "#{ep}/ext/bytesize"
require "#{ep}/ext/json"
require "#{ep}/server/server"
require "#{ep}/server/sublist"
require "#{ep}/server/connection"
require "#{ep}/server/options"
require "#{ep}/server/cluster"
require "#{ep}/server/route"
require "#{ep}/server/const"
require "#{ep}/server/util"
require "#{ep}/server/varz"
require "#{ep}/server/connz"
# Do setup
NATSD::Server.setup(ARGV.dup)
# Event Loop
EM.run do
log "WARNING: nats-server is deprecated and no longer supported. It will be removed in a future release. See https://github.com/nats-io/gnatsd"
log "Starting #{NATSD::APP_NAME} version #{NATSD::VERSION} on port #{NATSD::Server.port}"
log "TLS/SSL Support Enabled" if NATSD::Server.options[:ssl]
begin
EM.set_descriptor_table_size(32768) # Requires Root privileges
EM.start_server(NATSD::Server.host, NATSD::Server.port, NATSD::Connection)
rescue => e
log "Could not start server on port #{NATSD::Server.port}"
log_error
exit(1)
end
# Check to see if we need to fire up the http monitor port and server
if NATSD::Server.options[:http_port]
begin
NATSD::Server.start_http_server
rescue => e
log "Could not start monitoring server on port #{NATSD::Server.options[:http_port]}"
log_error
exit(1)
end
end
###################
# CLUSTER SETUP
###################
# Check to see if we need to fire up a routing listen port
if NATSD::Server.options[:cluster_port]
begin
log "Starting routing on port #{NATSD::Server.options[:cluster_port]}"
EM.start_server(NATSD::Server.host, NATSD::Server.options[:cluster_port], NATSD::Route)
rescue => e
log "Could not start routing server on port #{NATSD::Server.options[:cluster_port]}"
log_error
exit(1)
end
end
# If we have active connections, solicit them now..
NATSD::Server.solicit_routes if NATSD::Server.options[:cluster_routes]
end
================================================
FILE: lib/nats/version.rb
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
module NATS
# NOTE: These are all announced to the server on CONNECT
VERSION = "0.11.2".freeze
LANG = RUBY_ENGINE
PROTOCOL_VERSION = 1
end
================================================
FILE: nats.gemspec
================================================
# Copyright 2010-2018 The NATS Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
lib = File.expand_path('../lib/', __FILE__)
$:.unshift lib unless $:.include?(lib)
require 'nats/version'
spec = Gem::Specification.new do |s|
s.name = 'nats'
s.version = NATS::VERSION
s.summary = 'NATS is an open-source, high-performance, lightweight cloud messaging system.'
s.homepage = 'https://nats.io'
s.description = 'NATS is an open-source, high-performance, lightweight cloud messaging system.'
s.licenses = ['MIT']
s.authors = ['Derek Collison']
s.email = ['derek.collison@gmail.com']
s.add_dependency('eventmachine', '~> 1.2', '>= 1.2')
s.require_paths = ['lib']
s.bindir = 'bin'
s.executables = ['nats-pub', 'nats-sub', 'nats-queue', 'nats-request']
s.files = %w[
README.md
HISTORY.md
nats.gemspec
Rakefile
bin/nats-server
bin/nats-sub
bin/nats-pub
bin/nats-queue
bin/nats-top
bin/nats-request
lib/nats/client.rb
lib/nats/nuid.rb
lib/nats/version.rb
lib/nats/ext/bytesize.rb
lib/nats/ext/em.rb
lib/nats/ext/json.rb
lib/nats/server.rb
lib/nats/server/server.rb
lib/nats/server/connection.rb
lib/nats/server/cluster.rb
lib/nats/server/route.rb
lib/nats/server/options.rb
lib/nats/server/sublist.rb
lib/nats/server/const.rb
lib/nats/server/util.rb
lib/nats/server/varz.rb
lib/nats/server/connz.rb
]
end
================================================
FILE: scripts/install_gnatsd.sh
================================================
#!/bin/bash
set -e
export DEFAULT_NATS_SERVER_VERSION=v2.0.0
export NATS_SERVER_VERSION="${NATS_SERVER_VERSION:=$DEFAULT_NATS_SERVER_VERSION}"
# check to see if nats-server folder is empty
if [ ! "$(ls -A $HOME/nats-server)" ]; then
(
mkdir -p $HOME/nats-server
cd $HOME/nats-server
wget https://github.com/nats-io/nats-server/releases/download/$NATS_SERVER_VERSION/nats-server-$NATS_SERVER_VERSION-linux-amd64.zip -O nats-server.zip
unzip nats-server.zip
cp nats-server-$NATS_SERVER_VERSION-linux-amd64/nats-server $HOME/nats-server/nats-server
)
else
echo 'Using cached directory.';
fi
================================================
FILE: spec/.rspec
================================================
-fd
-c
================================================
FILE: spec/client/attack_spec.rb
================================================
require 'spec_helper'
describe 'Client - server attacks' do
TEST_SERVER = 'nats://127.0.0.1:4222'
MSG_SIZE = 10 * 1024 * 1024
BIG_MSG = 'a' * MSG_SIZE
BAD_SUBJECT = 'b' * 1024 * 1024
before (:each) do
@s = NatsServerControl.new(TEST_SERVER, "/tmp/nats_attack.pid")
@s.start_server(true)
end
after (:each) do
@s.kill_server
end
it "should not let us write large control line buffers" do
errors = []
with_em_timeout(3) do
NATS.on_error do |e|
errors << e
end
nc = NATS.connect(:servers => [TEST_SERVER], :reconnect => false)
nc.flush do
nc.publish(BAD_SUBJECT, 'a')
end
end
# Just confirm that it is no longer connected
expect(NATS.connected?).to be false
expect(errors.count >= 1).to eql(true)
end
it "should not let us write large messages" do
errors = []
with_em_timeout(3) do
NATS.on_error do |e|
errors << e
end
nc = NATS.connect(:uri => TEST_SERVER, :reconnect => false)
nc.flush do
nc.publish('foo', BIG_MSG)
end
end
expect(errors.count > 0).to be true
# NOTE: Race here on whether getting NATS::ServerError or NATS::ConnectError
# in case we have been disconnected before reading the error sent by server.
case errors.count
when 1
expect(errors[0]).to be_a NATS::ConnectError
when 2
expect(errors[0]).to be_a NATS::ServerError
expect(errors[1]).to be_a NATS::ConnectError
end
end
it "should complain if we can't kill our server that we started" do
errors = []
@s.kill_server
with_em_timeout(5) do
NATS.on_error do |e|
errors << e
end
NATS.connect(:uri => TEST_SERVER)
end
expect(errors.first).to be_a(NATS::ConnectError)
end
end
================================================
FILE: spec/client/auth_spec.rb
================================================
require 'spec_helper'
require 'fileutils'
describe 'Client - authorization' do
USER = 'derek'
PASS = 'mypassword'
TEST_AUTH_SERVER = "nats://#{USER}:#{PASS}@127.0.0.1:9222"
TEST_AUTH_SERVER_NO_CRED = 'nats://127.0.0.1:9222'
TEST_AUTH_SERVER_PID = '/tmp/nats_authorization.pid'
TEST_AUTH_AUTO_SERVER_PID = '/tmp/nats_auto_authorization.pid'
before (:each) do
@s = NatsServerControl.new(TEST_AUTH_SERVER, TEST_AUTH_SERVER_PID)
@s.start_server
end
after (:each) do
@s.kill_server
FileUtils.rm_f TEST_AUTH_SERVER_PID
end
it 'should fail to connect to an authorized server without proper credentials' do
errors = []
with_em_timeout do |future|
NATS.on_error do |e|
errors << e
end
NATS.connect(:uri => TEST_AUTH_SERVER_NO_CRED)
end
expect(errors.count).to eql(2)
expect(errors.first).to be_a NATS::AuthError
expect(errors.last).to be_a NATS::ConnectError
end
it 'should take user and password as separate options' do
errors = []
with_em_timeout(1) do
NATS.on_error do |e|
errors << e
end
NATS.connect(:uri => TEST_AUTH_SERVER_NO_CRED, :user => USER, :pass => PASS)
end
expect(errors.count).to eql(0)
end
it 'should not continue to try to connect on unauthorized access' do
auth_error_callbacks = 0
connect_error_callbacks = 0
with_em_timeout do
# Default error handler raises, so we trap here.
NATS.on_error do |e|
# disconnects
connect_error_callbacks += 1 if e.instance_of? NATS::ConnectError
# authorization
auth_error_callbacks +=1 if e.instance_of? NATS::AuthError
end
NATS.connect(:uri => TEST_AUTH_SERVER_NO_CRED)
end
expect(auth_error_callbacks).to eql(1)
expect(connect_error_callbacks).to eql(1)
end
it 'should remove server from the pool on unauthorized access' do
error_cb = 0
connect_cb = false
EM.run do
# Default error handler raises, so we trap here.
NATS.on_error { error_cb += 1 }
connected = false
NATS.start(:dont_randomize_servers => true, :servers => [TEST_AUTH_SERVER_NO_CRED, TEST_AUTH_SERVER]) do
connect_cb = true
EM.stop
end
end
expect(error_cb).to eql(1)
expect(connect_cb).to eql(true)
expect(NATS.client).to_not be(nil)
expect(NATS.client.server_pool.size).to eql(1)
NATS.stop # clears err_cb
end
end
================================================
FILE: spec/client/autounsub_spec.rb
================================================
require 'spec_helper'
describe 'Client - max responses and auto-unsubscribe' do
before(:each) do
@s = NatsServerControl.new("nats://127.0.0.1:4222")
@s.start_server(true)
end
after(:each) do
@s.kill_server
end
it "should only receive N msgs when requested: client support" do
WANT = 10
SEND = 20
received = 0
NATS.start(:servers => [@s.uri]) do
NATS.subscribe('foo', :max => WANT) { received += 1 }
(0...SEND).each { NATS.publish('foo', 'hello') }
NATS.publish('done') { NATS.stop }
end
expect(received).to eql(WANT)
end
it "should only receive N msgs when auto-unsubscribed" do
received = 0
NATS.start do
sid = NATS.subscribe('foo') { received += 1 }
NATS.unsubscribe(sid, WANT)
(0...SEND).each { NATS.publish('foo', 'hello') }
NATS.publish('done') { NATS.stop }
end
expect(received).to eql(WANT)
end
it "should not complain when unsubscribing an auto-unsubscribed sid" do
received = 0
NATS.start do
sid = NATS.subscribe('foo', :max => 1) { received += 1 }
(0...SEND).each { NATS.publish('foo', 'hello') }
NATS.publish('done') {
NATS.unsubscribe(sid)
NATS.stop
}
end
expect(received).to eql(1)
end
it "should allow proper override of auto-unsubscribe max variables to lesser value" do
received = 0
NATS.start do
sid = NATS.subscribe('foo') {
received += 1
NATS.unsubscribe(sid, 1)
}
NATS.unsubscribe(sid, SEND+1)
(0...SEND).each { NATS.publish('foo', 'hello') }
NATS.publish('done') { NATS.stop }
end
expect(received).to eql(1)
end
it "should allow proper override of auto-unsubscribe max variables to higher value" do
received = 0
NATS.start do
sid = NATS.subscribe('foo') { received += 1 }
NATS.unsubscribe(sid, 2)
NATS.unsubscribe(sid, WANT)
(0...SEND).each { NATS.publish('foo', 'hello') }
NATS.publish('done') { NATS.stop }
end
expect(received).to eql(WANT)
end
it "should only receive N msgs using request mode with multiple helpers" do
received = 0
NATS.start do
# Create 5 identical helpers
(0...5).each { NATS.subscribe('help') { |msg, reply| NATS.publish(reply, 'I can help!') } }
NATS.request('help', nil, :max => 1) { received += 1 }
EM.add_timer(0.1) { NATS.stop }
end
expect(received).to eql(1)
end
it "should not leak subscriptions on request that auto-unsubscribe properly with :max" do
received = 0
NATS.start do
sid = NATS.subscribe('help') { |msg, reply| NATS.publish(reply, 'I can help!') }
(1..100).each do
NATS.request('help', 'help request', :max => 1) { received += 1 }
end
NATS.flush do
EM.add_timer(0.1) do
NATS.unsubscribe(sid)
expect(NATS.client.subscription_count).to eql(0)
NATS.stop
end
end
end
expect(received).to eql(100)
end
it "should not complain when unsubscribe called on auto-cleaned up subscription" do
NATS.start do
sid = NATS.subscribe('help') { |msg, reply| NATS.publish(reply, 'I can help!') }
rsid = NATS.request('help', 'help request', :max => 1) {}
NATS.flush do
EM.add_timer(0.1) do
expect(NATS.client.subscription_count).to eql(1)
NATS.unsubscribe(sid)
expect(NATS.client.subscription_count).to eql(0)
NATS.unsubscribe(rsid)
NATS.stop
end
end
end
end
end
================================================
FILE: spec/client/binary_msg_spec.rb
================================================
require 'spec_helper'
describe 'Client - test binary message payloads to avoid connection drop' do
before(:all) do
@s = NatsServerControl.new
@s.start_server
end
after(:all) do
@s.kill_server
end
it "should not disconnect us if we send binary data" do
got_error = false
NATS.on_error { got_error = true; NATS.stop }
NATS.start(:reconnect => false) do
expect(NATS.connected?).to eql(true)
NATS.publish('dont_disconnect_me', "\006")
NATS.flush { NATS.stop }
end
expect(got_error).to eql(false)
end
end
================================================
FILE: spec/client/client_cluster_config_spec.rb
================================================
require 'spec_helper'
describe 'Client - cluster config' do
CLUSTER_USER = 'derek'
CLUSTER_PASS = 'mypassword'
CLUSTER_AUTH_PORT = 9292
CLUSTER_AUTH_SERVER = "nats://#{CLUSTER_USER}:#{CLUSTER_PASS}@127.0.0.1:#{CLUSTER_AUTH_PORT}"
CLUSTER_AUTH_SERVER_PID = '/tmp/nats_cluster_authorization.pid'
before(:all) do
@s = NatsServerControl.new
@as = NatsServerControl.new(CLUSTER_AUTH_SERVER, CLUSTER_AUTH_SERVER_PID)
end
before(:each) do
[@s, @as].each do |s|
s.start_server(true) unless NATS.server_running? s.uri
end
end
after(:each) do
[@s, @as].each do |s|
s.kill_server
end
end
it 'should properly process :uri option for multiple servers' do
NATS.start(:uri => ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223'], :dont_randomize_servers => true) do
options = NATS.options
expect(options).to be_a(Hash)
expect(options).to have_key(:uri)
expect(options[:uri]).to eql(['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223'])
NATS.stop
end
end
it 'should allow :uris and :servers as aliases' do
NATS.start(:uris => ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223'], :dont_randomize_servers => true) do
options = NATS.options
expect(options).to be_a(Hash)
expect(options).to have_key(:uris)
expect(options[:uris]).to eql(['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223'])
NATS.stop
end
NATS.start(:servers => ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223'], :dont_randomize_servers => true) do
options = NATS.options
expect(options).to be_a(Hash)
expect(options).to have_key(:servers)
expect(options[:servers]).to eql(['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223'])
NATS.stop
end
end
it 'should allow aliases on instance connections' do
c1 = c2 = nil
NATS.start do
c1 = NATS.connect(:uris => ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4222'])
c2 = NATS.connect(:servers => ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4222'])
timeout_nats_on_failure
end
expect(c1).to_not be(nil)
expect(c2).to_not be(nil)
end
it 'should randomize server pool list by default' do
servers = ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223',
'nats://127.0.0.1:4224', 'nats://127.0.0.1:4225',
'nats://127.0.0.1:4226', 'nats://127.0.0.1:4227']
NATS.start do
NATS.connect(:uri => servers.dup) do |c|
sp_servers = []
c.server_pool.each { |s| sp_servers << s[:uri].to_s }
expect(sp_servers).to_not eql(servers)
end
timeout_nats_on_failure
end
end
it 'should not randomize server pool if options suppress' do
servers = ['nats://127.0.0.1:4222', 'nats://127.0.0.1:4223',
gitextract_06zlpyqi/
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── config.yml
│ ├── defect.yml
│ └── proposal.yml
├── .gitignore
├── .travis.yml
├── CODE-OF-CONDUCT.md
├── GOVERNANCE.md
├── Gemfile
├── HISTORY.md
├── LICENSE
├── MAINTAINERS.md
├── README.md
├── Rakefile
├── TODO
├── benchmark/
│ ├── latency_perf.rb
│ ├── pub_perf.rb
│ ├── pub_sub_perf.rb
│ ├── queues_perf.rb
│ ├── sub_perf.rb
│ └── sublist_perf.rb
├── bin/
│ ├── nats-pub
│ ├── nats-queue
│ ├── nats-request
│ ├── nats-server
│ ├── nats-sub
│ └── nats-top
├── dependencies.md
├── examples/
│ ├── auth_pub.rb
│ ├── auth_sub.rb
│ ├── auto_unsub.rb
│ ├── busy_body.rb
│ ├── drain_connection.rb
│ ├── expected.rb
│ ├── fiber_request.rb
│ ├── multi_connection.rb
│ ├── pub.rb
│ ├── queue_sub.rb
│ ├── request.rb
│ ├── server_config.yml
│ ├── server_config_cluster.yml
│ ├── simple.rb
│ ├── sub.rb
│ ├── sub_timeout.rb
│ ├── tls-connect.rb
│ └── tls.rb
├── lib/
│ └── nats/
│ ├── client.rb
│ ├── ext/
│ │ ├── bytesize.rb
│ │ ├── em.rb
│ │ └── json.rb
│ ├── nuid.rb
│ ├── server/
│ │ ├── cluster.rb
│ │ ├── connection.rb
│ │ ├── connz.rb
│ │ ├── const.rb
│ │ ├── options.rb
│ │ ├── route.rb
│ │ ├── server.rb
│ │ ├── sublist.rb
│ │ ├── util.rb
│ │ └── varz.rb
│ ├── server.rb
│ └── version.rb
├── nats.gemspec
├── scripts/
│ └── install_gnatsd.sh
└── spec/
├── .rspec
├── client/
│ ├── attack_spec.rb
│ ├── auth_spec.rb
│ ├── autounsub_spec.rb
│ ├── binary_msg_spec.rb
│ ├── client_cluster_config_spec.rb
│ ├── client_cluster_reconnect_spec.rb
│ ├── client_config_spec.rb
│ ├── client_connect_spec.rb
│ ├── client_drain_spec.rb
│ ├── client_nkeys_connect_spec.rb
│ ├── client_requests_spec.rb
│ ├── client_spec.rb
│ ├── client_tls_spec.rb
│ ├── cluster_auth_token_spec.rb
│ ├── cluster_auto_discovery_spec.rb
│ ├── cluster_lb_spec.rb
│ ├── cluster_multi_route_spec.rb
│ ├── cluster_retry_connect_spec.rb
│ ├── cluster_spec.rb
│ ├── error_on_client_spec.rb
│ ├── fast_producer_spec.rb
│ ├── nuid_spec.rb
│ ├── partial_message_spec.rb
│ ├── queues_spec.rb
│ ├── reconnect_spec.rb
│ ├── server_info_spec.rb
│ └── sub_timeouts_spec.rb
├── configs/
│ ├── certs/
│ │ ├── bad-ca.pem
│ │ ├── ca.pem
│ │ ├── client-cert.pem
│ │ ├── client-key.pem
│ │ ├── key.pem
│ │ ├── multi-ca.pem
│ │ └── server.pem
│ ├── nkeys/
│ │ ├── foo-user.creds
│ │ ├── foo-user.jwt
│ │ ├── foo-user.nk
│ │ └── op.jwt
│ ├── tls-no-auth.conf
│ ├── tls.conf
│ └── tlsverify.conf
├── server/
│ ├── max_connections_spec.rb
│ ├── monitor_spec.rb
│ ├── multi_user_auth_spec.rb
│ ├── protocol_spec.rb
│ ├── resources/
│ │ ├── auth.yml
│ │ ├── b1_cluster.yml
│ │ ├── b2_cluster.yml
│ │ ├── cluster.yml
│ │ ├── config.yml
│ │ ├── max_connections.yml
│ │ ├── mixed_auth.yml
│ │ ├── monitor.yml
│ │ ├── multi_user_auth.yml
│ │ ├── multi_user_auth_long.yml
│ │ ├── ping.yml
│ │ ├── s1_cluster.yml
│ │ ├── s2_cluster.yml
│ │ └── s3_cluster.yml
│ ├── server_cluster_config_spec.rb
│ ├── server_config_spec.rb
│ ├── server_exitcode_spec.rb
│ ├── server_log_spec.rb
│ ├── server_ping_spec.rb
│ ├── ssl_spec.rb
│ └── sublist_spec.rb
└── spec_helper.rb
SYMBOL INDEX (301 symbols across 31 files)
FILE: benchmark/latency_perf.rb
function done (line 45) | def done
function send_request (line 51) | def send_request
FILE: benchmark/pub_perf.rb
function send_batch (line 64) | def send_batch
function display_final_results (line 85) | def display_final_results
FILE: benchmark/pub_sub_perf.rb
function send_batch (line 67) | def send_batch
function display_final_results (line 88) | def display_final_results
FILE: benchmark/sublist_perf.rb
class PerfSublist (line 18) | class PerfSublist
method subsInit (line 24) | def PerfSublist.subsInit(pre=nil)
method disableSublistCache (line 33) | def PerfSublist.disableSublistCache
method addWildcards (line 37) | def PerfSublist.addWildcards
method matchTest (line 43) | def PerfSublist.matchTest(subject, loop)
method reset (line 50) | def PerfSublist.reset
method removeAll (line 54) | def PerfSublist.removeAll
method subscriptionCount (line 60) | def PerfSublist.subscriptionCount
method totalCount (line 64) | def PerfSublist.totalCount
function ms_time (line 70) | def ms_time(t)
FILE: examples/auth_pub.rb
function usage (line 4) | def usage
FILE: examples/auth_sub.rb
function usage (line 6) | def usage
FILE: examples/auto_unsub.rb
function usage (line 6) | def usage
FILE: examples/busy_body.rb
function create_subscribers (line 11) | def create_subscribers(sub='foo.bar', num_subs=10, num_connections=20)
function create_publishers (line 19) | def create_publishers(sub='foo.bar', body='Hello World!', num_connection...
function timed_publish (line 27) | def timed_publish(sub='foo.bar', body='Hello World!', delay=1, burst=500)
FILE: examples/expected.rb
function usage (line 6) | def usage
FILE: examples/pub.rb
function usage (line 4) | def usage
FILE: examples/queue_sub.rb
function usage (line 6) | def usage
FILE: examples/sub.rb
function usage (line 6) | def usage
FILE: examples/sub_timeout.rb
function usage (line 6) | def usage
FILE: lib/nats/client.rb
type NATS (line 28) | module NATS
class Error (line 77) | class Error < StandardError; end
class ServerError (line 80) | class ServerError < Error; end
class ClientError (line 83) | class ClientError < Error; end
class ConnectError (line 86) | class ConnectError < Error; end
class AuthError (line 89) | class AuthError < ConnectError; end
function connect (line 151) | def connect(uri=nil, opts={}, &blk)
function start (line 236) | def start(*args, &blk)
function stop (line 256) | def stop(&blk)
function drain (line 269) | def drain(&blk)
function connected_server (line 276) | def connected_server
function connected? (line 282) | def connected?
function reconnecting? (line 288) | def reconnecting?
function draining? (line 294) | def draining?
function options (line 300) | def options
function server_info (line 306) | def server_info
function on_error (line 313) | def on_error(&callback)
function on_reconnect (line 319) | def on_reconnect(&callback)
function on_disconnect (line 326) | def on_disconnect(&callback)
function on_close (line 333) | def on_close(&callback)
function publish (line 340) | def publish(*args, &blk)
function subscribe (line 346) | def subscribe(*args, &blk)
function unsubscribe (line 352) | def unsubscribe(*args)
function timeout (line 358) | def timeout(*args, &blk)
function request (line 364) | def request(*args, &blk)
function create_inbox (line 370) | def create_inbox
function flush (line 376) | def flush(*args, &blk)
function pending_data_size (line 382) | def pending_data_size(*args)
function wait_for_server (line 386) | def wait_for_server(uri, max_wait = 5) # :nodoc:
function server_running? (line 394) | def server_running?(uri) # :nodoc:
function clear_client (line 403) | def clear_client # :nodoc:
function uri_is_remote? (line 409) | def uri_is_remote?(uri)
function process_uri (line 413) | def process_uri(uris)
function initialize (line 456) | def initialize(options)
function publish (line 505) | def publish(subject, msg=EMPTY_MSG, opt_reply=nil, &blk)
function subscribe (line 525) | def subscribe(subject, opts={}, &callback)
function unsubscribe (line 540) | def unsubscribe(sid, opt_max=nil)
function drain (line 551) | def drain(&blk)
function subscription_count (line 593) | def subscription_count
function timeout (line 601) | def timeout(sid, timeout, opts={}, &callback)
function request (line 623) | def request(subject, data=nil, opts={}, &cb)
function start_resp_mux_sub! (line 691) | def start_resp_mux_sub!
function flush (line 732) | def flush(&blk)
function on_connect (line 738) | def on_connect(&callback)
function on_error (line 744) | def on_error(&callback)
function on_reconnect (line 750) | def on_reconnect(&callback)
function on_disconnect (line 756) | def on_disconnect(&callback)
function on_close (line 762) | def on_close(&callback)
function close (line 767) | def close
function pending_data_size (line 776) | def pending_data_size
function stats (line 781) | def stats
function user_err_cb? (line 790) | def user_err_cb? # :nodoc:
function auth_connection? (line 794) | def auth_connection?
function connect_command (line 798) | def connect_command #:nodoc:
function send_connect_command (line 833) | def send_connect_command #:nodoc:
function queue_server_rt (line 837) | def queue_server_rt(&cb) #:nodoc:
function on_msg (line 843) | def on_msg(subject, sid, reply, msg) #:nodoc:
function flush_pending (line 876) | def flush_pending #:nodoc:
function receive_data (line 882) | def receive_data(data) #:nodoc:
function process_connect_init (line 944) | def process_connect_init(info) # :nodoc:
function process_info (line 992) | def process_info(info_line) #:nodoc:
function client_using_secure_connection? (line 1029) | def client_using_secure_connection?
function server_using_secure_connection? (line 1033) | def server_using_secure_connection?
function ssl_verify_peer (line 1037) | def ssl_verify_peer(cert)
function cancel_ping_timer (line 1049) | def cancel_ping_timer
function connection_completed (line 1056) | def connection_completed #:nodoc:
function ssl_handshake_completed (line 1068) | def ssl_handshake_completed
function process_connect (line 1072) | def process_connect #:nodoc:
function send_ping (line 1115) | def send_ping #:nodoc:
function process_pong (line 1127) | def process_pong
function should_delay_connect? (line 1132) | def should_delay_connect?(server)
function schedule_reconnect (line 1143) | def schedule_reconnect #:nodoc:
function unbind (line 1149) | def unbind #:nodoc:
function multiple_servers_available? (line 1167) | def multiple_servers_available?
function had_error? (line 1171) | def had_error?
function should_not_reconnect? (line 1175) | def should_not_reconnect?
function cancel_reconnect_timer (line 1179) | def cancel_reconnect_timer
function disconnect_error_string (line 1186) | def disconnect_error_string
function process_disconnect (line 1191) | def process_disconnect #:nodoc:
function can_reuse_server? (line 1210) | def can_reuse_server?(server) #:nodoc:
function attempt_reconnect (line 1216) | def attempt_reconnect #:nodoc:
function send_command (line 1235) | def send_command(command, priority = false) #:nodoc:
function setup_nkeys_connect (line 1252) | def setup_nkeys_connect
function process_uri_options (line 1332) | def process_uri_options #:nodoc
function connected_server (line 1341) | def connected_server
function discovered_servers (line 1347) | def discovered_servers
function bind_primary (line 1351) | def bind_primary #:nodoc:
function schedule_primary_and_connect (line 1360) | def schedule_primary_and_connect #:nodoc:
function inspect (line 1387) | def inspect #:nodoc:
class MonotonicTime (line 1391) | class MonotonicTime
method now (line 1395) | def now
method now (line 1399) | def now
method now (line 1403) | def now
FILE: lib/nats/ext/bytesize.rb
class String (line 16) | class String #:nodoc:
method bytesize (line 17) | def bytesize; self.size; end
FILE: lib/nats/ext/em.rb
class EM::Connection (line 24) | class EM::Connection
method get_outbound_data_size (line 25) | def get_outbound_data_size; return 0; end
FILE: lib/nats/nuid.rb
type NATS (line 16) | module NATS
class NUID (line 17) | class NUID
method initialize (line 28) | def initialize
method next (line 36) | def next
method randomize_prefix! (line 57) | def randomize_prefix!
method reset_sequential! (line 67) | def reset_sequential!
FILE: lib/nats/server/cluster.rb
type NATSD (line 3) | module NATSD #:nodoc: all
class Server (line 5) | class Server
method connected_routes (line 13) | def connected_routes
method add_route (line 17) | def add_route(route)
method remove_route (line 21) | def remove_route(route)
method route_info_string (line 25) | def route_info_string
method route_key (line 38) | def route_key(route_url)
method route_auth_ok? (line 43) | def route_auth_ok?(user, pass)
method solicit_routes (line 47) | def solicit_routes #:nodoc:
method try_to_connect_routes (line 55) | def try_to_connect_routes #:nodoc:
method broadcast_proto_to_routes (line 63) | def broadcast_proto_to_routes(proto)
method rsid_qsub (line 67) | def rsid_qsub(rsid)
method parse_rsid (line 76) | def parse_rsid(rsid)
method routed_sid (line 81) | def routed_sid(sub)
method route_sub_proto (line 85) | def route_sub_proto(sub)
method broadcast_sub_to_routes (line 90) | def broadcast_sub_to_routes(sub)
method broadcast_unsub_to_routes (line 94) | def broadcast_unsub_to_routes(sub)
FILE: lib/nats/server/connection.rb
type NATSD (line 1) | module NATSD #:nodoc: all
type Connection (line 3) | module Connection #:nodoc: all
function flush_data (line 9) | def flush_data
function queue_data (line 15) | def queue_data(data)
function client_info (line 22) | def client_info
function info (line 27) | def info
function max_connections_exceeded? (line 41) | def max_connections_exceeded?
function post_init (line 48) | def post_init
function send_ping (line 70) | def send_ping
function connect_auth_timeout (line 81) | def connect_auth_timeout
function connect_ssl_timeout (line 86) | def connect_ssl_timeout
function receive_data (line 91) | def receive_data(data)
function send_info (line 196) | def send_info
function process_info (line 201) | def process_info(info)
function auth_ok? (line 204) | def auth_ok?(user, pass)
function process_connect_config (line 208) | def process_connect_config(config)
function delete_subscriber (line 224) | def delete_subscriber(sub)
function error_close (line 230) | def error_close(msg)
function debug_print_controlline_too_big (line 237) | def debug_print_controlline_too_big(line_size)
function debug_print_msg_too_big (line 242) | def debug_print_msg_too_big(msg_size)
function inc_connections (line 247) | def inc_connections
function dec_connections (line 252) | def dec_connections
function process_unbind (line 257) | def process_unbind
function unbind (line 269) | def unbind
function ssl_handshake_completed (line 274) | def ssl_handshake_completed
function ssl_verify_peer (line 282) | def ssl_verify_peer(cert)
function ctrace (line 286) | def ctrace(*args)
function strip_op (line 290) | def strip_op(op='')
function is_route? (line 294) | def is_route?
function type (line 298) | def type
FILE: lib/nats/server/connz.rb
type NATSD (line 1) | module NATSD #:nodoc: all
class Connz (line 3) | class Connz
method call (line 4) | def call(env)
class Server (line 34) | class Server
method dump_connections (line 36) | def dump_connections
FILE: lib/nats/server/const.rb
type NATSD (line 2) | module NATSD #:nodoc:
FILE: lib/nats/server/options.rb
type NATSD (line 4) | module NATSD
class Server (line 5) | class Server
method parser (line 8) | def parser
method read_config_file (line 59) | def read_config_file
method setup_logs (line 131) | def setup_logs
method open_syslog (line 138) | def open_syslog
method close_syslog (line 143) | def close_syslog
method symbolize_users (line 147) | def symbolize_users(users)
method finalize_options (line 156) | def finalize_options
FILE: lib/nats/server/route.rb
type NATSD (line 1) | module NATSD #:nodoc: all
class Route (line 4) | class Route < EventMachine::Connection #:nodoc:
method initialize (line 12) | def initialize(route=nil)
method solicited? (line 16) | def solicited?
method connection_completed (line 20) | def connection_completed
method post_init (line 30) | def post_init
method send_local_subs_to_route (line 57) | def send_local_subs_to_route
method process_connect_route_config (line 66) | def process_connect_route_config(config)
method connect_auth_timeout (line 83) | def connect_auth_timeout
method receive_data (line 88) | def receive_data(data)
method send_auth (line 214) | def send_auth
method send_info (line 220) | def send_info
method process_info (line 224) | def process_info(info_json)
method auth_ok? (line 230) | def auth_ok?(user, pass)
method inc_connections (line 234) | def inc_connections
method dec_connections (line 239) | def dec_connections
method try_reconnect (line 244) | def try_reconnect
method cancel_reconnect (line 249) | def cancel_reconnect
method unbind (line 255) | def unbind
method ctrace (line 265) | def ctrace(*args)
method is_route? (line 269) | def is_route?
method type (line 273) | def type
FILE: lib/nats/server/server.rb
type NATSD (line 3) | module NATSD #:nodoc: all
class Server (line 8) | class Server
method version (line 20) | def version; "nats-server version #{NATSD::VERSION}" end
method host (line 22) | def host; @options[:addr] end
method port (line 23) | def port; @options[:port] end
method pid_file (line 24) | def pid_file; @options[:pid_file] end
method process_options (line 26) | def process_options(argv=[])
method setup (line 38) | def setup(argv)
method subscribe (line 94) | def subscribe(sub, is_route=false)
method unsubscribe (line 99) | def unsubscribe(sub, is_route=false)
method deliver_to_subscriber (line 104) | def deliver_to_subscriber(sub, subject, reply, msg)
method route_to_subscribers (line 130) | def route_to_subscribers(subject, reply, msg, is_route=false)
method auth_ok? (line 182) | def auth_ok?(user, pass)
method cid (line 187) | def cid
method rid (line 191) | def rid
method info_string (line 195) | def info_string
method start_http_server (line 200) | def start_http_server
FILE: lib/nats/server/sublist.rb
class Sublist (line 13) | class Sublist #:nodoc:
method initialize (line 25) | def initialize(options = {})
method disable_cache (line 42) | def disable_cache; @cache = nil; end
method enable_cache (line 43) | def enable_cache; @cache ||= {}; end
method clear_cache (line 44) | def clear_cache; @cache = {} if @cache; end
method prune_cache (line 47) | def prune_cache
method insert (line 54) | def insert(subject, subscriber)
method remove (line 73) | def remove(subject, subscriber)
method match (line 79) | def match(subject)
method matchAll (line 94) | def matchAll(level, tokens)
method prune_level (line 113) | def prune_level(level, node, token)
method remove_level (line 126) | def remove_level(level, tokens, subscriber)
method node_count_level (line 153) | def node_count_level(level, nc)
method node_count (line 163) | def node_count
FILE: lib/nats/server/util.rb
function fast_uuid (line 3) | def fast_uuid #:nodoc:
function syslog (line 9) | def syslog(args, priority) #:nodoc:
function log (line 13) | def log(*args) #:nodoc:
function debug (line 19) | def debug(*args) #:nodoc:
function trace (line 25) | def trace(*args) #:nodoc:
function log_error (line 31) | def log_error(e=$!) #:nodoc:
function uptime_string (line 35) | def uptime_string(delta)
function pretty_size (line 46) | def pretty_size(size, prec=1)
function num_cpu_cores (line 54) | def num_cpu_cores
function shutdown (line 66) | def shutdown #:nodoc:
function dump_connection_state (line 80) | def dump_connection_state
FILE: lib/nats/server/varz.rb
type NATSD (line 1) | module NATSD #:nodoc: all
class Varz (line 3) | class Varz
method call (line 4) | def call(env)
class Server (line 12) | class Server
method update_varz (line 15) | def update_varz
FILE: lib/nats/version.rb
type NATS (line 15) | module NATS
FILE: spec/client/client_spec.rb
function process_pong (line 396) | def c.process_pong
function send_ping (line 411) | def c.send_ping
FILE: spec/client/reconnect_spec.rb
class SomeException (line 78) | class SomeException < StandardError; end
FILE: spec/spec_helper.rb
function timeout_nats_on_failure (line 7) | def timeout_nats_on_failure(to=0.25)
function timeout_em_on_failure (line 13) | def timeout_em_on_failure(to=0.25)
function with_em_timeout (line 19) | def with_em_timeout(to=1)
function wait_on_connections (line 36) | def wait_on_connections(conns)
function flush_routes (line 47) | def flush_routes(conns, &blk)
function wait_on_routes_connected (line 51) | def wait_on_routes_connected(conns)
class NatsServerControl (line 73) | class NatsServerControl
method init_with_config (line 80) | def init_with_config(config_file)
method init_with_config_from_string (line 90) | def init_with_config_from_string(config_string, config={})
method initialize (line 110) | def initialize(uri='nats://127.0.0.1:4222', pid_file='/tmp/test-nats.p...
method server_pid (line 117) | def server_pid
method server_mem_mb (line 121) | def server_mem_mb
method start_server (line 127) | def start_server(wait_for_server=true, monitoring: false)
method kill_server (line 150) | def kill_server
class RubyNatsServerControl (line 162) | class RubyNatsServerControl < NatsServerControl
method start_server (line 163) | def start_server(wait_for_server=true)
type EchoServer (line 185) | module EchoServer
function post_init (line 191) | def post_init
function receive_data (line 195) | def receive_data(data)
function start (line 200) | def start(&blk)
function stop (line 207) | def stop
type SilentServer (line 213) | module SilentServer
function post_init (line 219) | def post_init
function receive_data (line 224) | def receive_data(data)
function start (line 228) | def start(&blk)
function stop (line 235) | def stop
type OldInfoServer (line 241) | module OldInfoServer
function post_init (line 247) | def post_init
function receive_data (line 251) | def receive_data(data)
function start (line 255) | def start(&blk)
function stop (line 262) | def stop
type OldProtocolInfoServer (line 268) | module OldProtocolInfoServer
function post_init (line 274) | def post_init
function receive_data (line 278) | def receive_data(data)
function start (line 282) | def start(&blk)
function stop (line 289) | def stop
Condensed preview — 132 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (427K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 329,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Discussion\n url: https://github.com/nats-io/nats.rb/discussions\n"
},
{
"path": ".github/ISSUE_TEMPLATE/defect.yml",
"chars": 1316,
"preview": "---\nname: Defect\ndescription: Report a defect, such as a bug or regression.\nlabels:\n - defect\nbody:\n - type: textarea\n"
},
{
"path": ".github/ISSUE_TEMPLATE/proposal.yml",
"chars": 951,
"preview": "---\nname: Proposal\ndescription: Propose an enhancement or new feature.\nlabels:\n - proposal\nbody:\n - type: textarea\n "
},
{
"path": ".gitignore",
"chars": 55,
"preview": "*~\n\\#*\\#\n.\\#*\n*.rbc\n.rbx\n.bundle\n*.gem\n.DS_Store\n.idea\n"
},
{
"path": ".travis.yml",
"chars": 244,
"preview": "language: ruby\n\nrvm:\n - 2.7\n\ncache:\n directories:\n - $HOME/nats-server\n\nbefore_install:\n - bash ./scripts/install_gn"
},
{
"path": "CODE-OF-CONDUCT.md",
"chars": 138,
"preview": "## Community Code of Conduct\n\nNATS follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/cod"
},
{
"path": "GOVERNANCE.md",
"chars": 183,
"preview": "# NATS Ruby Client Governance\n\nNATS Ruby Client is part of the NATS project and is subject to the [NATS Governance](http"
},
{
"path": "Gemfile",
"chars": 211,
"preview": "source \"http://rubygems.org\"\n\ngemspec\n\ngroup :test do\n gem 'rake'\n gem 'rspec'\nend\n\ngroup :server do\n gem 'daemons'\n "
},
{
"path": "HISTORY.md",
"chars": 4514,
"preview": "# HISTORY\n\n## v0.11.0 (June 10, 2019)\n - NATS v2.0 support! (#162)\n\n## v0.8.4 (Feb 23, 2018)\n - Support to include con"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "MAINTAINERS.md",
"chars": 236,
"preview": "# Maintainers\n\nMaintainership is on a per project basis.\n\n### Maintainers\n - Derek Collison <derek@nats.io> [@derekcoll"
},
{
"path": "README.md",
"chars": 9537,
"preview": "# NATS - Ruby Client\n\nA [Ruby](http://ruby-lang.org) client for the [NATS messaging system](https://nats.io).\n\n[![Licens"
},
{
"path": "Rakefile",
"chars": 1969,
"preview": "#!/usr/bin/env rake\n# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"Licens"
},
{
"path": "TODO",
"chars": 1369,
"preview": "\n- [DONE] Contributing guidelines\n- [DONE] cluster tests for travis-ci\n\n- [DONE] clustering/routing\n - [DONE] Allow imp"
},
{
"path": "benchmark/latency_perf.rb",
"chars": 1801,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "benchmark/pub_perf.rb",
"chars": 2596,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "benchmark/pub_sub_perf.rb",
"chars": 2714,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "benchmark/queues_perf.rb",
"chars": 1835,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "benchmark/sub_perf.rb",
"chars": 1583,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "benchmark/sublist_perf.rb",
"chars": 3516,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "bin/nats-pub",
"chars": 1226,
"preview": "#!/usr/bin/env ruby\n# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"Licens"
},
{
"path": "bin/nats-queue",
"chars": 1702,
"preview": "#!/usr/bin/env ruby\n# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"Licens"
},
{
"path": "bin/nats-request",
"chars": 1737,
"preview": "#!/usr/bin/env ruby\n# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"Licens"
},
{
"path": "bin/nats-server",
"chars": 712,
"preview": "#!/usr/bin/env ruby\n# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"Licens"
},
{
"path": "bin/nats-sub",
"chars": 1620,
"preview": "#!/usr/bin/env ruby\n# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"Licens"
},
{
"path": "bin/nats-top",
"chars": 5114,
"preview": "#!/usr/bin/env ruby\n# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"Licens"
},
{
"path": "dependencies.md",
"chars": 171,
"preview": "# External Dependencies\n\nThis file lists the dependencies used in this repository.\n\n| Dependency | License |\n|-|-|\n| git"
},
{
"path": "examples/auth_pub.rb",
"chars": 464,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\ndef usage\n puts \"Usage: pub.rb <user> <pass> <subject> <msg>\"; exit\nend\n\nuser"
},
{
"path": "examples/auth_sub.rb",
"chars": 508,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { NATS.stop } }\n\ndef usage\n puts \"Usag"
},
{
"path": "examples/auto_unsub.rb",
"chars": 707,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { NATS.stop } }\n\ndef usage\n puts \"Usag"
},
{
"path": "examples/busy_body.rb",
"chars": 933,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n# This is an example to show off nats-top. Run busy_body on a monitor enabled\n"
},
{
"path": "examples/drain_connection.rb",
"chars": 2179,
"preview": "require 'nats/client'\n\nnc1 = nil\nnc2 = nil\nresponses = []\ninbox = NATS.create_inbox\n[\"TERM\", \"INT\"].each { |sig| trap(si"
},
{
"path": "examples/expected.rb",
"chars": 842,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { NATS.stop } }\n\ndef usage\n puts \"Usag"
},
{
"path": "examples/fiber_request.rb",
"chars": 1881,
"preview": "require 'fiber'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { EM.stop } }\n\nNATS.on_error { |err| puts "
},
{
"path": "examples/multi_connection.rb",
"chars": 395,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { NATS.stop } }\n\nNATS.on_error { |err| "
},
{
"path": "examples/pub.rb",
"chars": 329,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\ndef usage\n puts \"Usage: ruby pub.rb <subject> <msg>\"; exit\nend\n\nsubject, msg "
},
{
"path": "examples/queue_sub.rb",
"chars": 469,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { NATS.stop } }\n\ndef usage\n puts \"Usag"
},
{
"path": "examples/request.rb",
"chars": 387,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { NATS.stop } }\n\nNATS.on_error { |err| "
},
{
"path": "examples/server_config.yml",
"chars": 449,
"preview": "\n---\n\n#\n# Sample Server Sonfiguration\n# nats-server -c ./server_config.yml\n#\n\nport: 4242\nnet: localhost\n\nauthorization:\n"
},
{
"path": "examples/server_config_cluster.yml",
"chars": 1233,
"preview": "\n---\n\n#\n# Sample Server Sonfiguration\n# nats-server -c ./server_config.yml\n#\n\nport: 4242\nnet: localhost\n\nauthorization:\n"
},
{
"path": "examples/simple.rb",
"chars": 327,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { NATS.stop } }\n\nNATS.on_error { |err| "
},
{
"path": "examples/sub.rb",
"chars": 374,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { NATS.stop } }\n\ndef usage\n puts \"Usag"
},
{
"path": "examples/sub_timeout.rb",
"chars": 598,
"preview": "require 'rubygems'\nrequire 'nats/client'\n\n[\"TERM\", \"INT\"].each { |sig| trap(sig) { NATS.stop } }\n\ndef usage\n puts \"Usag"
},
{
"path": "examples/tls-connect.rb",
"chars": 1181,
"preview": "require 'nats/client'\n\nEM.run do\n\n options = {\n :servers => [\n 'nats://secret:deadbeef@127.0.0.1:4443',\n '"
},
{
"path": "examples/tls.rb",
"chars": 1142,
"preview": "require 'nats/client'\n\noptions = {\n :servers => [\n 'nats://secret:deadbeef@127.0.0.1:4443',\n 'nats://secret:deadbee"
},
{
"path": "lib/nats/client.rb",
"chars": 45203,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "lib/nats/ext/bytesize.rb",
"chars": 676,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "lib/nats/ext/em.rb",
"chars": 907,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "lib/nats/ext/json.rb",
"chars": 764,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "lib/nats/nuid.rb",
"chars": 2258,
"preview": "# Copyright 2016-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "lib/nats/server/cluster.rb",
"chars": 2772,
"preview": "require 'uri'\n\nmodule NATSD #:nodoc: all\n\n class Server\n class << self\n attr_reader :opt_routes, :route_auth_re"
},
{
"path": "lib/nats/server/connection.rb",
"chars": 9817,
"preview": "module NATSD #:nodoc: all\n\n module Connection #:nodoc: all\n\n attr_accessor :in_msgs, :out_msgs, :in_bytes, :out_byte"
},
{
"path": "lib/nats/server/connz.rb",
"chars": 1537,
"preview": "module NATSD #:nodoc: all\n\n class Connz\n def call(env)\n c_info = Server.dump_connections\n qs = env['QUERY_"
},
{
"path": "lib/nats/server/const.rb",
"chars": 3238,
"preview": "\nmodule NATSD #:nodoc:\n\n VERSION = '0.5.1'\n APP_NAME = 'nats-server'\n\n DEFAULT_PORT = 4222\n DEFAULT_HOST = '0.0.0.0"
},
{
"path": "lib/nats/server/options.rb",
"chars": 8859,
"preview": "require 'optparse'\nrequire 'yaml'\n\nmodule NATSD\n class Server\n\n class << self\n def parser\n @parser ||= O"
},
{
"path": "lib/nats/server/route.rb",
"chars": 9120,
"preview": "module NATSD #:nodoc: all\n\n # Need to make this a class with EM > 1.0\n class Route < EventMachine::Connection #:nodoc:"
},
{
"path": "lib/nats/server/server.rb",
"chars": 7303,
"preview": "require 'set'\n\nmodule NATSD #:nodoc: all\n\n # Subscriber\n Subscriber = Struct.new(:conn, :subject, :sid, :qgroup, :num_"
},
{
"path": "lib/nats/server/sublist.rb",
"chars": 5215,
"preview": "#--\n#\n# Sublist implementation for a publish-subscribe system.\n# This container class holds subscriptions and matches\n# "
},
{
"path": "lib/nats/server/util.rb",
"chars": 2307,
"preview": "require 'pp'\n\ndef fast_uuid #:nodoc:\n v = [rand(0x0010000),rand(0x0010000),rand(0x0010000),\n rand(0x0010000),rand"
},
{
"path": "lib/nats/server/varz.rb",
"chars": 884,
"preview": "module NATSD #:nodoc: all\n\n class Varz\n def call(env)\n varz_json = JSON.pretty_generate(Server.update_varz) + \""
},
{
"path": "lib/nats/server.rb",
"chars": 2648,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "lib/nats/version.rb",
"chars": 732,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "nats.gemspec",
"chars": 1936,
"preview": "# Copyright 2010-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "scripts/install_gnatsd.sh",
"chars": 607,
"preview": "#!/bin/bash\n\nset -e\n\nexport DEFAULT_NATS_SERVER_VERSION=v2.0.0\n\nexport NATS_SERVER_VERSION=\"${NATS_SERVER_VERSION:=$DEFA"
},
{
"path": "spec/.rspec",
"chars": 7,
"preview": "-fd\n-c\n"
},
{
"path": "spec/client/attack_spec.rb",
"chars": 1812,
"preview": "\nrequire 'spec_helper'\n\ndescribe 'Client - server attacks' do\n\n TEST_SERVER = 'nats://127.0.0.1:4222'\n MSG_SIZE = 1"
},
{
"path": "spec/client/auth_spec.rb",
"chars": 2460,
"preview": "\nrequire 'spec_helper'\nrequire 'fileutils'\n\ndescribe 'Client - authorization' do\n USER = 'derek'\n PASS = 'mypassword'\n"
},
{
"path": "spec/client/autounsub_spec.rb",
"chars": 3552,
"preview": "require 'spec_helper'\n\ndescribe 'Client - max responses and auto-unsubscribe' do\n\n before(:each) do\n @s = NatsServer"
},
{
"path": "spec/client/binary_msg_spec.rb",
"chars": 567,
"preview": "require 'spec_helper'\n\ndescribe 'Client - test binary message payloads to avoid connection drop' do\n\n before(:all) do\n "
},
{
"path": "spec/client/client_cluster_config_spec.rb",
"chars": 7578,
"preview": "require 'spec_helper'\n\ndescribe 'Client - cluster config' do\n\n CLUSTER_USER = 'derek'\n CLUSTER_PASS = 'mypassword'\n\n "
},
{
"path": "spec/client/client_cluster_reconnect_spec.rb",
"chars": 7794,
"preview": "require 'spec_helper'\n\ndescribe 'Client - cluster reconnect' do\n\n before(:all) do\n auth_options = {\n 'user' "
},
{
"path": "spec/client/client_config_spec.rb",
"chars": 5615,
"preview": "require 'spec_helper'\n\ndescribe \"Client - configuration\" do\n\n before(:each) do\n @s = NatsServerControl.new\n @s.st"
},
{
"path": "spec/client/client_connect_spec.rb",
"chars": 5969,
"preview": "require 'spec_helper'\n\ndescribe 'Client - connect' do\n\n before(:each) do\n @s = NatsServerControl.new\n @s.start_se"
},
{
"path": "spec/client/client_drain_spec.rb",
"chars": 14365,
"preview": "require 'spec_helper'\n\ndescribe 'Client - drain' do\n\n before(:each) do\n @s = NatsServerControl.new\n @s.start_serv"
},
{
"path": "spec/client/client_nkeys_connect_spec.rb",
"chars": 4150,
"preview": "require 'spec_helper'\n\ndescribe 'Client - NATS v2 Auth' do\n\n context 'with NKEYS and JWT' do\n before(:each) do\n "
},
{
"path": "spec/client/client_requests_spec.rb",
"chars": 5491,
"preview": "require 'spec_helper'\n\ndescribe 'Client - requests' do\n\n before(:each) do\n @s = NatsServerControl.new\n @s.start_s"
},
{
"path": "spec/client/client_spec.rb",
"chars": 13204,
"preview": "require 'spec_helper'\n\ndescribe 'Client - specification' do\n\n before(:each) do\n @s = NatsServerControl.new\n @s.st"
},
{
"path": "spec/client/client_tls_spec.rb",
"chars": 28958,
"preview": "require 'spec_helper'\n\ndescribe 'Client - TLS spec', :jruby_excluded do\n\n context 'when server does not support TLS' do"
},
{
"path": "spec/client/cluster_auth_token_spec.rb",
"chars": 4195,
"preview": "require 'spec_helper'\nrequire 'yaml'\n\ndescribe 'Client - auth token' do\n\n before(:all) do\n\n auth_options = {\n '"
},
{
"path": "spec/client/cluster_auto_discovery_spec.rb",
"chars": 9691,
"preview": "require 'spec_helper'\nrequire 'yaml'\n\ndescribe 'Client - cluster auto discovery' do\n\n before(:each) do\n\n auth_option"
},
{
"path": "spec/client/cluster_lb_spec.rb",
"chars": 2679,
"preview": "require 'spec_helper'\nrequire 'uri'\n\ndescribe 'Client - cluster load balance' do\n\n before(:each) do\n auth_options = "
},
{
"path": "spec/client/cluster_multi_route_spec.rb",
"chars": 3400,
"preview": "require 'spec_helper'\n\ndescribe 'Client - cluster' do\n\n before(:all) do\n auth_options = {\n 'user' => 'derek"
},
{
"path": "spec/client/cluster_retry_connect_spec.rb",
"chars": 4140,
"preview": "require 'spec_helper'\n\ndescribe 'Client - cluster retry connect' do\n\n before(:all) do\n auth_options = {\n 'user'"
},
{
"path": "spec/client/cluster_spec.rb",
"chars": 6827,
"preview": "require 'spec_helper'\nrequire 'yaml'\n\ndescribe 'Client - cluster' do\n\n before(:all) do\n\n auth_options = {\n 'use"
},
{
"path": "spec/client/error_on_client_spec.rb",
"chars": 1459,
"preview": "require 'spec_helper'\n\ndescribe 'Client - error on client' do\n\n context 'NATS::ServerError' do\n\n it 'should show the"
},
{
"path": "spec/client/fast_producer_spec.rb",
"chars": 2242,
"preview": "require 'spec_helper'\n\ndescribe 'Client - fast producer' do\n\n before(:all) do\n @s = NatsServerControl.new\n @s.sta"
},
{
"path": "spec/client/nuid_spec.rb",
"chars": 1755,
"preview": "# Copyright 2016-2018 The NATS Authors\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not u"
},
{
"path": "spec/client/partial_message_spec.rb",
"chars": 1371,
"preview": "require 'spec_helper'\n\ndescribe 'Client - partial message behavior' do\n before do\n @s = NatsServerControl.new\n @s"
},
{
"path": "spec/client/queues_spec.rb",
"chars": 4382,
"preview": "require 'spec_helper'\n\ndescribe \"Client - queue group support\" do\n\n before(:each) do\n @s = NatsServerControl.new\n "
},
{
"path": "spec/client/reconnect_spec.rb",
"chars": 3209,
"preview": "require 'spec_helper'\n\ndescribe 'Client - reconnect specification' do\n\n before(:all) do\n R_USER = 'derek'\n R_PASS"
},
{
"path": "spec/client/server_info_spec.rb",
"chars": 1337,
"preview": "require 'spec_helper'\n\ndescribe 'Client - server_info support' do\n\n before(:all) do\n @s = NatsServerControl.new\n "
},
{
"path": "spec/client/sub_timeouts_spec.rb",
"chars": 3109,
"preview": "require 'spec_helper'\n\ndescribe 'Client - subscriptions with timeouts' do\n\n before(:all) do\n TIMEOUT = 0.1\n WAIT"
},
{
"path": "spec/configs/certs/bad-ca.pem",
"chars": 2334,
"preview": "-----BEGIN CERTIFICATE-----\nMIIGjzCCBHegAwIBAgIJAKT2W9SKY7o4MA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD\nVQQGEwJVUzELMAkGA1UECBMCQ0E"
},
{
"path": "spec/configs/certs/ca.pem",
"chars": 1223,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDXDCCAkQCCQDI2Vsry8+BDDANBgkqhkiG9w0BAQsFADBwMQswCQYDVQQGEwJV\nUzELMAkGA1UECAwCQ0ExEDAOBgN"
},
{
"path": "spec/configs/certs/client-cert.pem",
"chars": 1191,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDQjCCAiqgAwIBAgIJAJCSLX9jr5WzMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJDQTE"
},
{
"path": "spec/configs/certs/client-key.pem",
"chars": 1707,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCsnD6dO3oSVoV4\nyt+c/Ax+XvJPIjNGgThT16clj9f"
},
{
"path": "spec/configs/certs/key.pem",
"chars": 1699,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDm+0dlzcmiLa+L\nzdVqeVQ8B1/rWnErK+VvvjH7FmV"
},
{
"path": "spec/configs/certs/multi-ca.pem",
"chars": 2423,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDSTCCAjGgAwIBAgIQDbspQLZaH4pjQlTk/trkBzANBgkqhkiG9w0BAQsFADBO\nMQwwCgYDVQQGEwNVU0ExFjAUBgN"
},
{
"path": "spec/configs/certs/server.pem",
"chars": 1183,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDPTCCAiWgAwIBAgIJAJCSLX9jr5W7MA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJDQTE"
},
{
"path": "spec/configs/nkeys/foo-user.creds",
"chars": 912,
"preview": "-----BEGIN NATS USER JWT-----\neyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJXTURGT1dHV1JGWkRGRFVSM0dPUkdESEtUTTdDUl"
},
{
"path": "spec/configs/nkeys/foo-user.jwt",
"chars": 485,
"preview": "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJXTURGT1dHV1JGWkRGRFVSM0dPUkdESEtUTTdDUlZBVDQ1RkRFMllNRUY1N0VOQ0JBVFFR"
},
{
"path": "spec/configs/nkeys/foo-user.nk",
"chars": 58,
"preview": "SUAMLK2ZNL35WSMW37E7UD4VZ7ELPKW7DHC3BWBSD2GCZ7IUQQXZIORRBU"
},
{
"path": "spec/configs/nkeys/op.jwt",
"chars": 530,
"preview": "-----BEGIN TEST OPERATOR JWT-----\neyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiIyTVhXQ0VaRFo2S0g3NU5ZU1NaUFBTTElET1"
},
{
"path": "spec/configs/tls-no-auth.conf",
"chars": 173,
"preview": "\n# Simple TLS config file\n\nport: 4444\nnet: \"127.0.0.1\"\n\ntls {\n cert_file: \"./spec/configs/certs/server.pem\"\n key_file"
},
{
"path": "spec/configs/tls.conf",
"chars": 246,
"preview": "\n# Simple TLS config file\n\nport: 4443\nnet: \"127.0.0.1\"\n\ntls {\n cert_file: \"./spec/configs/certs/server.pem\"\n key_file"
},
{
"path": "spec/configs/tlsverify.conf",
"chars": 318,
"preview": "\n# Simple TLS config file\n\nport: 4445\nnet: \"127.0.0.1\"\n\ntls {\n cert_file: \"./spec/configs/certs/server.pem\"\n key_file"
},
{
"path": "spec/server/max_connections_spec.rb",
"chars": 1261,
"preview": "require 'spec_helper'\nrequire 'yaml'\n\ndescribe 'Server - max connections support' do\n\n before (:all) do\n MC_SERVER_P"
},
{
"path": "spec/server/monitor_spec.rb",
"chars": 14874,
"preview": "\nrequire 'spec_helper'\nrequire 'fileutils'\n\nrequire 'net/http'\nrequire 'uri'\n\nrequire 'nats/server/server'\nrequire 'nats"
},
{
"path": "spec/server/multi_user_auth_spec.rb",
"chars": 4072,
"preview": "\nrequire 'spec_helper'\nrequire 'fileutils'\nrequire 'nats/server/server'\nrequire \"nats/server/sublist\"\nrequire \"nats/serv"
},
{
"path": "spec/server/protocol_spec.rb",
"chars": 7495,
"preview": "require 'spec_helper'\nrequire 'nats/server/const'\n\ndescribe 'Server - NATS Protocol' do\n\n context 'sub' do\n\n it 'sho"
},
{
"path": "spec/server/resources/auth.yml",
"chars": 74,
"preview": "\n---\n\nport: 4242\nnet: localhost\n\nauthorization:\n user: derek\n pass: foo\n"
},
{
"path": "spec/server/resources/b1_cluster.yml",
"chars": 646,
"preview": "port: 6242\nnet: localhost\n\nauthorization:\n user: derek\n password: bella\n token: deadbeef\n timeout: 1\n\n# This is the "
},
{
"path": "spec/server/resources/b2_cluster.yml",
"chars": 594,
"preview": "port: 6244\nnet: localhost\n\nauthorization:\n user: derek\n password: bella\n token: deadbeef\n timeout: 1\n\n# This is the "
},
{
"path": "spec/server/resources/cluster.yml",
"chars": 1227,
"preview": "\n---\n\n#\n# Sample Server Sonfiguration\n# nats-server -c ./cluster.yml\n#\n\nport: 4242\nnet: localhost\n\nauthorization:\n user"
},
{
"path": "spec/server/resources/config.yml",
"chars": 396,
"preview": "\n---\n\nport: 4242\nnet: localhost\n\nauthorization:\n user: derek\n password: bella\n token: deadbeef\n timeout: 1\n\npid_file"
},
{
"path": "spec/server/resources/max_connections.yml",
"chars": 72,
"preview": "---\n\nport: 9272\nnet: localhost\n\n# Protocol/Limits\nmax_connections: 32\n\n\n"
},
{
"path": "spec/server/resources/mixed_auth.yml",
"chars": 158,
"preview": "\n---\n\nport: 4242\nnet: localhost\n\nauthorization:\n\n user: derek\n pass: foo\n\n users:\n - user: sam\n pass: wrestli"
},
{
"path": "spec/server/resources/monitor.yml",
"chars": 352,
"preview": "\n---\n\nport: 4242\nnet: localhost\n\nhttp:\n net: 127.0.0.1\n port: 4222\n user: derek\n password: foo\n\npid_file: '/tmp/nat"
},
{
"path": "spec/server/resources/multi_user_auth.yml",
"chars": 337,
"preview": "\n---\n\nport: 9226\nnet: localhost\n\nauthorization:\n users:\n - user: derek\n pass: foo\n - user: sam\n pass: w"
},
{
"path": "spec/server/resources/multi_user_auth_long.yml",
"chars": 345,
"preview": "\n---\n\nport: 9226\nnet: localhost\n\nauthorization:\n users:\n - user: derek\n pass: foo\n - user: sam\n passwor"
},
{
"path": "spec/server/resources/ping.yml",
"chars": 363,
"preview": "\n---\n\nport: 2421\nnet: localhost\n\npid_file: '/tmp/nats_ping_test.pid'\nlog_file: '/tmp/nats_ping_test.log'\n\n# Ping options"
},
{
"path": "spec/server/resources/s1_cluster.yml",
"chars": 481,
"preview": "\n---\n\n#\n# Sample Server Sonfiguration\n# nats-server -c ./s[N]_cluster.yml\n#\n\nport: 4242\nnet: localhost\n\nauthorization:\n "
},
{
"path": "spec/server/resources/s2_cluster.yml",
"chars": 557,
"preview": "\n---\n\n#\n# Sample Server Sonfiguration\n# nats-server -c ./s[N]_cluster.yml\n#\n\nport: 4244\nnet: localhost\n\nauthorization:\n "
},
{
"path": "spec/server/resources/s3_cluster.yml",
"chars": 557,
"preview": "\n---\n\n#\n# Sample Server Sonfiguration\n# nats-server -c ./s[N]_cluster.yml\n#\n\nport: 4246\nnet: localhost\n\nauthorization:\n "
},
{
"path": "spec/server/server_cluster_config_spec.rb",
"chars": 2304,
"preview": "require 'spec_helper'\nrequire 'nats/server/server'\nrequire 'nats/server/sublist'\nrequire 'nats/server/options'\nrequire '"
},
{
"path": "spec/server/server_config_spec.rb",
"chars": 4208,
"preview": "require 'spec_helper'\nrequire 'nats/server/server'\nrequire \"nats/server/sublist\"\nrequire \"nats/server/options\"\nrequire \""
},
{
"path": "spec/server/server_exitcode_spec.rb",
"chars": 520,
"preview": "require 'spec_helper'\nrequire 'fileutils'\n\ndescribe 'Server - exit codes' do\n\n before (:all) do\n config_file = File."
},
{
"path": "spec/server/server_log_spec.rb",
"chars": 1964,
"preview": "require 'spec_helper'\nrequire 'syslog'\nrequire 'nats/server/server'\nrequire 'nats/server/const'\nrequire 'nats/server/opt"
},
{
"path": "spec/server/server_ping_spec.rb",
"chars": 1853,
"preview": "\nrequire 'spec_helper'\nrequire 'fileutils'\n\nrequire 'nats/server/server'\nrequire 'nats/server/options'\nrequire 'nats/ser"
},
{
"path": "spec/server/ssl_spec.rb",
"chars": 1824,
"preview": "require 'spec_helper'\nrequire 'fileutils'\n\ndescribe 'Server - SSL' do\n\n TEST_SERVER_SSL = \"nats://127.0.0.1:9392"
},
{
"path": "spec/server/sublist_spec.rb",
"chars": 4922,
"preview": "require 'spec_helper'\nrequire 'nats/server/sublist'\n\ndescribe 'Server - sublist functionality' do\n\n before do\n @subl"
},
{
"path": "spec/spec_helper.rb",
"chars": 6511,
"preview": "$:.unshift('./lib')\n\nrequire 'net/http'\nrequire 'nats/client'\nrequire 'tempfile'\n\ndef timeout_nats_on_failure(to=0.25)\n "
}
]
About this extraction
This page contains the full source code of the nats-io/ruby-nats GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 132 files (394.2 KB), approximately 119.5k tokens, and a symbol index with 301 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.