bps-nats 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 202e41923cbb093a7504969a3dfa266574e6b5d1f0982cf5c08bb7393252eecf
4
+ data.tar.gz: 4beb06613f160e27a140e5efeb6b0055df4bcbdff596abecdaf2579685c7515d
5
+ SHA512:
6
+ metadata.gz: 78975cd84023ae46da605d0a1380b0854383f533f1fb528fb60be9a923e65414b7c7eeac03c4327262d5e586957624a69de5469fad83d3d168609bc9eedef482
7
+ data.tar.gz: bb70c2a34954e52f923612b7e2ebb77989a553dfa0f7d2179d393cb58089a5f02d011b6de4ccb549f17d59ac6c12574ddd3758a53ed031477acfec10214d8b8b
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'bps-nats'
3
+ s.version = File.read(File.expand_path('../../.version', __dir__)).strip
4
+ s.platform = Gem::Platform::RUBY
5
+
6
+ s.licenses = ['Apache-2.0']
7
+ s.summary = 'BPS adapter for nats'
8
+ s.description = 'https://github.com/bsm/bps'
9
+
10
+ s.authors = ['Black Square Media']
11
+ s.email = 'info@blacksquaremedia.com'
12
+ s.homepage = 'https://github.com/bsm/bps'
13
+
14
+ s.executables = []
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- spec/*`.split("\n")
17
+ s.require_paths = ['lib']
18
+ s.required_ruby_version = '>= 2.6.0'
19
+
20
+ s.add_dependency 'bps', s.version
21
+ s.add_dependency 'nats-pure', '~> 0.6.2'
22
+ end
@@ -0,0 +1 @@
1
+ require 'bps/nats'
@@ -0,0 +1,12 @@
1
+ require 'bps'
2
+ require 'bps/publisher/nats'
3
+
4
+ module BPS
5
+ module Publisher
6
+ register('nats') do |url, **opts|
7
+ url_opts = NATS.parse_url(url)
8
+ url_opts.update(opts)
9
+ NATS.new(**NATS.coercer.coerce(url_opts))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,78 @@
1
+ require 'cgi'
2
+ require 'nats/io/client'
3
+
4
+ module BPS
5
+ module Publisher
6
+ class NATS < Abstract
7
+ FLUSH_TIMEOUT = 5
8
+
9
+ class Topic < Abstract::Topic
10
+ def initialize(client, topic)
11
+ super()
12
+
13
+ @client = client
14
+ @topic = topic
15
+ end
16
+
17
+ def publish(message, **_opts)
18
+ @client.publish(@topic, message)
19
+ end
20
+
21
+ def flush(**)
22
+ @client.flush(FLUSH_TIMEOUT)
23
+ end
24
+ end
25
+
26
+ CLIENT_OPTS = {
27
+ servers: [:string],
28
+ dont_randomize_servers: :bool,
29
+ reconnect_time_wait: :float,
30
+ max_reconnect_attempts: :int,
31
+ connect_timeout: :float,
32
+ tls_ca_file: :string,
33
+ # TODO: review, list all of them: https://github.com/nats-io/nats-pure.rb
34
+ }.freeze
35
+
36
+ def self.parse_url(url)
37
+ port = url.port&.to_s || '4222'
38
+ servers = CGI.unescape(url.host).split(',').map do |host|
39
+ addr = "nats://#{host}"
40
+ addr << ':' << port unless /:\d+$/.match?(addr)
41
+ addr
42
+ end
43
+ opts = CGI.parse(url.query || '').transform_values {|v| v.size == 1 ? v[0] : v }
44
+ opts.merge(servers: servers)
45
+ end
46
+
47
+ # @return [BPS::Coercer] the options coercer.
48
+ def self.coercer
49
+ @coercer ||= BPS::Coercer.new(CLIENT_OPTS).freeze
50
+ end
51
+
52
+ # @param [Hash] options.
53
+ def initialize(**opts)
54
+ super()
55
+
56
+ # handle TLS if CA file is provided:
57
+ if !opts[:tls] && opts[:tls_ca_file]
58
+ ctx = OpenSSL::SSL::SSLContext.new
59
+ ctx.set_params
60
+ ctx.ca_file = opts.delete(:tls_ca_file)
61
+ opts[:tls] = ctx
62
+ end
63
+
64
+ @topics = {}
65
+ @client = ::NATS::IO::Client.new
66
+ @client.connect(**opts.slice(*CLIENT_OPTS.keys))
67
+ end
68
+
69
+ def topic(name)
70
+ @topics[name] ||= self.class::Topic.new(@client, name)
71
+ end
72
+
73
+ def close
74
+ @client.close
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,75 @@
1
+ require 'bps/nats'
2
+ require 'spec_helper'
3
+
4
+ RSpec.describe 'NATS', nats: true do
5
+ context 'with addr resolving' do
6
+ let(:publisher) { instance_double('BPS::Publisher::NATS') }
7
+
8
+ before { allow(BPS::Publisher::NATS).to receive(:new).and_return(publisher) }
9
+
10
+ it 'resolves simple URLs' do
11
+ allow(BPS::Publisher::NATS)
12
+ .to receive(:new)
13
+ .with(servers: ['nats://test.host:4222'])
14
+ .and_return(publisher)
15
+ BPS::Publisher.resolve(URI.parse('nats://test.host:4222'))
16
+ end
17
+
18
+ it 'resolves URLs with multiple hosts' do
19
+ allow(BPS::Publisher::NATS)
20
+ .to receive(:new)
21
+ .with(servers: ['nats://foo.host:4222', 'nats://bar.host:4222'])
22
+ .and_return(publisher)
23
+ BPS::Publisher.resolve(URI.parse('nats://foo.host,bar.host:4222'))
24
+ end
25
+
26
+ it 'resolves URLs with multiple hosts/ports' do
27
+ allow(BPS::Publisher::NATS)
28
+ .to receive(:new)
29
+ .with(servers: ['nats://foo.host:4223', 'nats://bar.host:4222'])
30
+ .and_return(publisher)
31
+ BPS::Publisher.resolve(URI.parse('nats://foo.host%3A4223,bar.host'))
32
+ end
33
+ end
34
+
35
+ context BPS::Publisher::NATS do
36
+ let(:nats_servers) { ENV.fetch('NATS_SERVERS', '127.0.0.1:4222').split(',') }
37
+ let(:messages_queue) { Queue.new }
38
+ let(:nats_servers_with_scheme) { nats_servers.map {|s| "nats://#{s}" } }
39
+
40
+ let(:publisher_url) { "nats://#{CGI.escape(nats_servers.join(','))}" }
41
+
42
+ # Pure NATS doesn't retain messages, so we need to subscribe BEFORE emitting.
43
+ # Also, subscription is asynchronous, so we have to use blocking queue to wait for messages.
44
+
45
+ # connect to NATS before any test code runs:
46
+ let!(:nats_client) do
47
+ opts = {
48
+ servers: nats_servers_with_scheme,
49
+ dont_randomize_servers: true,
50
+ }
51
+ client = ::NATS::IO::Client.new
52
+ client.connect(opts)
53
+ client
54
+ end
55
+
56
+ # don't forget to close NATS connection after tests are done:
57
+ after do
58
+ nats_client.close
59
+ end
60
+
61
+ # blocking queue to gather messages in background thread:
62
+
63
+ # subscribe to test topic, non-blocking; messages will be pushed into blocking queue:
64
+ def setup_topic(topic_name, num_messages)
65
+ nats_client.subscribe(topic_name, max: num_messages) {|msg| messages_queue << msg }
66
+ end
67
+
68
+ # simply drain messages queue to get messages:
69
+ def read_messages(_topic_name, num_messages)
70
+ Array.new(num_messages) { messages_queue.pop }
71
+ end
72
+
73
+ it_behaves_like 'publisher'
74
+ end
75
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bps-nats
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Black Square Media
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-01-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bps
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: nats-pure
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.2
41
+ description: https://github.com/bsm/bps
42
+ email: info@blacksquaremedia.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - bps-nats.gemspec
48
+ - lib/bps-nats.rb
49
+ - lib/bps/nats.rb
50
+ - lib/bps/publisher/nats.rb
51
+ - spec/bps/nats_spec.rb
52
+ homepage: https://github.com/bsm/bps
53
+ licenses:
54
+ - Apache-2.0
55
+ metadata: {}
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 2.6.0
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubygems_version: 3.1.4
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: BPS adapter for nats
75
+ test_files:
76
+ - spec/bps/nats_spec.rb