Full Code of nats-io/ruby-nats for AI

main 639d4d0dc154 cached
132 files
394.2 KB
119.5k tokens
301 symbols
1 requests
Download .txt
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).

[![License Apache 2.0](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
[![Build Status](https://app.travis-ci.com/nats-io/nats.rb.svg?branch=master)](https://app.travis-ci.com/nats-io/nats.rb)
[![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&type=5&v=0.11.0)](https://rubygems.org/gems/nats/versions/0.11.0)
[![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](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',
 
Download .txt
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
Download .txt
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.

Copied to clipboard!