bps-stan 0.1.3 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd81b99b13cfa8b5be0f5d16ac87bfe1b1fadf2fbe15f7e64a35aa03b7738434
4
- data.tar.gz: 981a5ffacde21e61b6e40e04d933cbc25ed684dfc8d2ed70829a088affa61890
3
+ metadata.gz: a3e0a906dbfb5399c6478e431f089d474c19477db1a317fcda9315212ca4eddd
4
+ data.tar.gz: dfc0b3d82292d7c5a222306cccf6354c63d5b681637efd8206362e52d38ac07c
5
5
  SHA512:
6
- metadata.gz: 5df714f945e601d841a5bce43760b354494f83b97eb4544c8c515fecc87026de44ebb343ab657fed2910880f6beb9cf7d212e703a05ee4036b41a5d6e14f795d
7
- data.tar.gz: 03cafb2a8283b7875f353f9c8ccef57752e1f5d4b18e165b85ff8e1cc4a16523c96f06c11aa1cd6ad409d1a9951b7ae700aa4bfd18661bb4aed15b921590b75b
6
+ metadata.gz: 8a979907c82f715c626c9dcaaeff154944e56f01c29527e5de6cc3cac10ec0f263e1b2f01553f2bed6b444aa87629ebb5f6234903391e7a9bc94f756d189c98e
7
+ data.tar.gz: '096d145a4db40e80dd33f65bb88420678eea66cde04e0f604f9bbdff63f5fc2c1c330586faa3de7b882ee58551fa1b5c883b175140cef6fc2195317d3cd478aa'
data/bps-stan.gemspec CHANGED
@@ -18,5 +18,6 @@ Gem::Specification.new do |s|
18
18
  s.required_ruby_version = '>= 2.6.0'
19
19
 
20
20
  s.add_dependency 'bps', s.version
21
- s.add_dependency 'nats-streaming', '= 0.2.2'
21
+ s.add_dependency 'nats-streaming', '~> 0.2.2'
22
+ s.metadata['rubygems_mfa_required'] = 'true'
22
23
  end
@@ -21,44 +21,14 @@ module BPS
21
21
  end
22
22
  end
23
23
 
24
- CLIENT_OPTS = {
25
- nats: {
26
- servers: [:string],
27
- dont_randomize_servers: :bool,
28
- reconnect_time_wait: :float,
29
- max_reconnect_attempts: :int,
30
- connect_timeout: :float,
31
- # TODO: review, list all of them: https://github.com/nats-io/nats.rb (there's tls config etc)
32
- },
33
- }.freeze
34
-
35
- def self.parse_url(url)
36
- port = url.port&.to_s || '4222'
37
- servers = CGI.unescape(url.host).split(',').map do |host|
38
- addr = "nats://#{host}"
39
- addr << ':' << port unless addr.match(/:\d+$/)
40
- addr
41
- end
42
- opts = CGI.parse(url.query).transform_values {|v| v.size == 1 ? v[0] : v }
43
- cluster_id = opts.delete('cluster_id')
44
- client_id = opts.delete('client_id')
45
- [cluster_id, client_id, { nats: { servers: servers } }]
46
- end
47
-
48
- # @return [BPS::Coercer] the options coercer.
49
- def self.coercer
50
- @coercer ||= BPS::Coercer.new(CLIENT_OPTS).freeze
51
- end
52
-
53
24
  # @param [String] cluster ID.
54
25
  # @param [String] client ID.
55
26
  # @param [Hash] options.
56
- def initialize(cluster_id, client_id, nats: {}, **opts)
27
+ def initialize(cluster_id, client_id, **opts)
57
28
  super()
58
29
 
59
30
  @topics = {}
60
- @client = ::STAN::Client.new
61
- @client.connect(cluster_id, client_id, nats: nats, **opts.slice(*CLIENT_OPTS.keys))
31
+ @client = ::BPS::STAN.connect(cluster_id, client_id, **opts)
62
32
  end
63
33
 
64
34
  def topic(name)
@@ -66,7 +36,9 @@ module BPS
66
36
  end
67
37
 
68
38
  def close
69
- # NATS/STAN do not survive multi-closes, so close only once:
39
+ super
40
+
41
+ # NATS/STAN does not survive multi-closes, so close only once:
70
42
  @client&.close
71
43
  @client = nil
72
44
  end
data/lib/bps/stan.rb CHANGED
@@ -1,12 +1,72 @@
1
1
  require 'bps'
2
2
  require 'bps/publisher/stan'
3
+ require 'bps/subscriber/stan'
3
4
 
4
5
  module BPS
5
6
  module Publisher
6
7
  register('stan') do |url, **opts|
7
- cluster_id, client_id, url_opts = STAN.parse_url(url)
8
+ cluster_id, client_id, url_opts = ::BPS::STAN.parse_url(url)
8
9
  url_opts.update(opts)
9
- STAN.new(cluster_id, client_id, **STAN.coercer.coerce(url_opts))
10
+ STAN.new(cluster_id, client_id, **::BPS::STAN.coercer.coerce(url_opts))
11
+ end
12
+ end
13
+
14
+ module Subscriber
15
+ register('stan') do |url, **opts|
16
+ cluster_id, client_id, url_opts = ::BPS::STAN.parse_url(url)
17
+ url_opts.update(opts)
18
+ STAN.new(cluster_id, client_id, **::BPS::STAN.coercer.coerce(url_opts))
19
+ end
20
+ end
21
+
22
+ module STAN
23
+ CLIENT_OPTS = {
24
+ nats: {
25
+ servers: [:string],
26
+ dont_randomize_servers: :bool,
27
+ reconnect_time_wait: :float,
28
+ max_reconnect_attempts: :int,
29
+ connect_timeout: :float,
30
+ tls_ca_file: :string,
31
+ # TODO: review, list all of them: https://github.com/nats-io/nats.rb
32
+ },
33
+ }.freeze
34
+
35
+ # @param [String] cluster ID
36
+ # @param [String] client ID
37
+ # @param [Hash] options
38
+ # @return [STAN::Client] connected STAN client
39
+ def self.connect(cluster_id, client_id, nats: {}, **opts)
40
+ # handle TLS if CA file is provided:
41
+ if !nats[:tls] && nats[:tls_ca_file]
42
+ ctx = OpenSSL::SSL::SSLContext.new
43
+ ctx.set_params
44
+ ctx.ca_file = nats.delete(:tls_ca_file)
45
+ nats[:tls] = ctx
46
+ end
47
+
48
+ client = ::STAN::Client.new
49
+ client.connect(cluster_id, client_id, nats: nats, **opts.slice(*CLIENT_OPTS.keys))
50
+ client
51
+ end
52
+
53
+ # @return [BPS::Coercer] the options coercer
54
+ def self.coercer
55
+ @coercer ||= BPS::Coercer.new(CLIENT_OPTS).freeze
56
+ end
57
+
58
+ # @return [Array] arguments for connecting to STAN
59
+ def self.parse_url(url)
60
+ port = url.port&.to_s || '4222'
61
+ servers = CGI.unescape(url.host).split(',').map do |host|
62
+ addr = "nats://#{host}"
63
+ addr << ':' << port unless /:\d+$/.match?(addr)
64
+ addr
65
+ end
66
+ opts = CGI.parse(url.query || '').transform_values {|v| v.size == 1 ? v[0] : v }
67
+ cluster_id = opts.delete('cluster_id')
68
+ client_id = opts.delete('client_id')
69
+ [cluster_id, client_id, { nats: { servers: servers } }]
10
70
  end
11
71
  end
12
72
  end
@@ -0,0 +1,37 @@
1
+ require 'cgi'
2
+ require 'stan/client'
3
+
4
+ module BPS
5
+ module Subscriber
6
+ class STAN < Abstract
7
+ # @param [String] cluster ID.
8
+ # @param [String] client ID.
9
+ # @param [Hash] options.
10
+ def initialize(cluster_id, client_id, **opts)
11
+ super()
12
+
13
+ @client = ::BPS::STAN.connect(cluster_id, client_id, **opts)
14
+ end
15
+
16
+ # Subscribe to a topic
17
+ # @param topic [String] topic the topic name.
18
+ def subscribe(topic, **opts)
19
+ # important opts:
20
+ # - queue: 'queue-name' # https://docs.nats.io/developing-with-nats-streaming/queues
21
+ # - durable_name: 'durable-name' # https://docs.nats.io/developing-with-nats-streaming/durables
22
+ @client.subscribe(topic, **opts) do |msg|
23
+ yield msg.data # TODO: maybe yielding just bytes is not too flexible? But IMO can wait till (much) later
24
+ end
25
+ end
26
+
27
+ # Close the subscriber.
28
+ def close
29
+ super
30
+
31
+ # NATS/STAN does not survive multi-closes, so close only once:
32
+ @client&.close
33
+ @client = nil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -2,32 +2,36 @@ require 'bps/stan'
2
2
  require 'spec_helper'
3
3
 
4
4
  RSpec.describe 'STAN', stan: true do
5
- context 'resolve addrs' do
6
- let(:publisher) { double('BPS::Publisher::STAN') }
5
+ context 'with addr resolving' do
6
+ let(:publisher) { instance_double('BPS::Publisher::STAN') }
7
+
7
8
  before { allow(BPS::Publisher::STAN).to receive(:new).and_return(publisher) }
8
9
 
9
- it 'should resolve simple URLs' do
10
- expect(BPS::Publisher::STAN)
10
+ it 'resolves simple URLs' do
11
+ allow(BPS::Publisher::STAN)
11
12
  .to receive(:new)
12
13
  .with('CLUSTER', 'CLIENT', nats: { servers: ['nats://test.host:4222'] })
13
14
  .and_return(publisher)
14
- BPS::Publisher.resolve(URI.parse('stan://test.host:4222?cluster_id=CLUSTER&client_id=CLIENT'))
15
+ expect(BPS::Publisher.resolve(URI.parse('stan://test.host:4222?cluster_id=CLUSTER&client_id=CLIENT')))
16
+ .to eq(publisher)
15
17
  end
16
18
 
17
- it 'should resolve URLs with multiple hosts' do
18
- expect(BPS::Publisher::STAN)
19
+ it 'resolves URLs with multiple hosts' do
20
+ allow(BPS::Publisher::STAN)
19
21
  .to receive(:new)
20
22
  .with('CLUSTER', 'CLIENT', nats: { servers: ['nats://foo.host:4222', 'nats://bar.host:4222'] })
21
23
  .and_return(publisher)
22
- BPS::Publisher.resolve(URI.parse('stan://foo.host,bar.host:4222?cluster_id=CLUSTER&client_id=CLIENT'))
24
+ expect(BPS::Publisher.resolve(URI.parse('stan://foo.host,bar.host:4222?cluster_id=CLUSTER&client_id=CLIENT')))
25
+ .to eq(publisher)
23
26
  end
24
27
 
25
- it 'should resolve URLs with multiple hosts/ports' do
26
- expect(BPS::Publisher::STAN)
28
+ it 'resolves URLs with multiple hosts/ports' do
29
+ allow(BPS::Publisher::STAN)
27
30
  .to receive(:new)
28
31
  .with('CLUSTER', 'CLIENT', nats: { servers: ['nats://foo.host:4223', 'nats://bar.host:4222'] })
29
32
  .and_return(publisher)
30
- BPS::Publisher.resolve(URI.parse('stan://foo.host%3A4223,bar.host?cluster_id=CLUSTER&client_id=CLIENT'))
33
+ expect(BPS::Publisher.resolve(URI.parse('stan://foo.host%3A4223,bar.host?cluster_id=CLUSTER&client_id=CLIENT')))
34
+ .to eq(publisher)
31
35
  end
32
36
  end
33
37
 
@@ -35,7 +39,7 @@ RSpec.describe 'STAN', stan: true do
35
39
  let(:cluster_id) { 'test-cluster' } # this is a default cluster for https://hub.docker.com/_/nats-streaming
36
40
  let(:client_id) { 'bps-test' }
37
41
 
38
- let(:nats_servers) { ENV.fetch('NATS_SERVERS', '127.0.0.1:4222').split(',') }
42
+ let(:nats_servers) { ENV.fetch('STAN_ADDRS', '127.0.0.1:4222').split(',') }
39
43
  let(:nats_servers_with_scheme) { nats_servers.map {|s| "nats://#{s}" } }
40
44
 
41
45
  let(:publisher_url) { "stan://#{CGI.escape(nats_servers.join(','))}/?cluster_id=#{cluster_id}&client_id=#{client_id}" }
@@ -0,0 +1,61 @@
1
+ require 'bps/stan'
2
+ require 'spec_helper'
3
+
4
+ RSpec.describe 'STAN', stan: true do
5
+ context 'with addr resolving' do
6
+ let(:subscriber) { instance_double('BPS::Subscriber::STAN') }
7
+
8
+ before { allow(BPS::Subscriber::STAN).to receive(:new).and_return(subscriber) }
9
+
10
+ it 'resolves simple URLs' do
11
+ allow(BPS::Subscriber::STAN)
12
+ .to receive(:new)
13
+ .with('CLUSTER', 'CLIENT', nats: { servers: ['nats://test.host:4222'] })
14
+ .and_return(subscriber)
15
+ expect(BPS::Subscriber.resolve(URI.parse('stan://test.host:4222?cluster_id=CLUSTER&client_id=CLIENT')))
16
+ .to eq(subscriber)
17
+ end
18
+
19
+ it 'resolves URLs with multiple hosts' do
20
+ allow(BPS::Subscriber::STAN)
21
+ .to receive(:new)
22
+ .with('CLUSTER', 'CLIENT', nats: { servers: ['nats://foo.host:4222', 'nats://bar.host:4222'] })
23
+ .and_return(subscriber)
24
+ expect(BPS::Subscriber.resolve(URI.parse('stan://foo.host,bar.host:4222?cluster_id=CLUSTER&client_id=CLIENT')))
25
+ .to eq(subscriber)
26
+ end
27
+
28
+ it 'resolves URLs with multiple hosts/ports' do
29
+ allow(BPS::Subscriber::STAN)
30
+ .to receive(:new)
31
+ .with('CLUSTER', 'CLIENT', nats: { servers: ['nats://foo.host:4223', 'nats://bar.host:4222'] })
32
+ .and_return(subscriber)
33
+ expect(BPS::Subscriber.resolve(URI.parse('stan://foo.host%3A4223,bar.host?cluster_id=CLUSTER&client_id=CLIENT')))
34
+ .to eq(subscriber)
35
+ end
36
+ end
37
+
38
+ context BPS::Subscriber::STAN do
39
+ let(:cluster_id) { 'test-cluster' } # this is a default cluster for https://hub.docker.com/_/nats-streaming
40
+ let(:client_id) { 'bps-test' }
41
+
42
+ let(:nats_servers) { ENV.fetch('STAN_ADDRS', '127.0.0.1:4222').split(',') }
43
+ let(:nats_servers_with_scheme) { nats_servers.map {|s| "nats://#{s}" } }
44
+
45
+ let(:subscriber_url) { "stan://#{CGI.escape(nats_servers.join(','))}/?cluster_id=#{cluster_id}&client_id=#{client_id}" }
46
+
47
+ def produce_messages(topic_name, messages)
48
+ opts = {
49
+ servers: nats_servers_with_scheme,
50
+ dont_randomize_servers: true,
51
+ }
52
+ ::STAN::Client.new.connect(cluster_id, client_id, nats: opts) do |client|
53
+ messages.each do |message|
54
+ client.publish(topic_name, message)
55
+ end
56
+ end
57
+ end
58
+
59
+ it_behaves_like 'subscriber'
60
+ end
61
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bps-stan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Black Square Media
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-24 00:00:00.000000000 Z
11
+ date: 2022-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bps
@@ -16,26 +16,26 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.3
19
+ version: 0.2.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.1.3
26
+ version: 0.2.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nats-streaming
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '='
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: 0.2.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '='
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.2.2
41
41
  description: https://github.com/bsm/bps
@@ -48,11 +48,14 @@ files:
48
48
  - lib/bps-stan.rb
49
49
  - lib/bps/publisher/stan.rb
50
50
  - lib/bps/stan.rb
51
- - spec/bps/stan_spec.rb
51
+ - lib/bps/subscriber/stan.rb
52
+ - spec/bps/publisher/stan_spec.rb
53
+ - spec/bps/subscriber/stan_spec.rb
52
54
  homepage: https://github.com/bsm/bps
53
55
  licenses:
54
56
  - Apache-2.0
55
- metadata: {}
57
+ metadata:
58
+ rubygems_mfa_required: 'true'
56
59
  post_install_message:
57
60
  rdoc_options: []
58
61
  require_paths:
@@ -68,9 +71,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
71
  - !ruby/object:Gem::Version
69
72
  version: '0'
70
73
  requirements: []
71
- rubygems_version: 3.1.2
74
+ rubygems_version: 3.3.3
72
75
  signing_key:
73
76
  specification_version: 4
74
77
  summary: BPS adapter for nats-streaming
75
78
  test_files:
76
- - spec/bps/stan_spec.rb
79
+ - spec/bps/publisher/stan_spec.rb
80
+ - spec/bps/subscriber/stan_spec.rb