nats-pure 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +10 -3
- data/lib/nats/client.rb +7 -3
- data/lib/nats/io/client.rb +464 -328
- data/lib/nats/io/errors.rb +2 -0
- data/lib/nats/io/jetstream/api.rb +53 -50
- data/lib/nats/io/jetstream/errors.rb +30 -14
- data/lib/nats/io/jetstream/js/config.rb +9 -3
- data/lib/nats/io/jetstream/js/header.rb +15 -9
- data/lib/nats/io/jetstream/js/status.rb +11 -5
- data/lib/nats/io/jetstream/js/sub.rb +4 -2
- data/lib/nats/io/jetstream/js.rb +10 -8
- data/lib/nats/io/jetstream/manager.rb +103 -101
- data/lib/nats/io/jetstream/msg/ack.rb +22 -12
- data/lib/nats/io/jetstream/msg/ack_methods.rb +30 -24
- data/lib/nats/io/jetstream/msg/metadata.rb +10 -7
- data/lib/nats/io/jetstream/msg.rb +11 -4
- data/lib/nats/io/jetstream/pull_subscription.rb +21 -10
- data/lib/nats/io/jetstream/push_subscription.rb +3 -1
- data/lib/nats/io/jetstream.rb +102 -106
- data/lib/nats/io/kv/api.rb +7 -3
- data/lib/nats/io/kv/bucket_status.rb +7 -5
- data/lib/nats/io/kv/errors.rb +25 -2
- data/lib/nats/io/kv/manager.rb +19 -10
- data/lib/nats/io/kv.rb +354 -22
- data/lib/nats/io/msg.rb +19 -19
- data/lib/nats/io/parser.rb +23 -23
- data/lib/nats/io/rails.rb +2 -0
- data/lib/nats/io/subscription.rb +33 -23
- data/lib/nats/io/version.rb +4 -2
- data/lib/nats/io/websocket.rb +40 -18
- data/lib/nats/nuid.rb +33 -22
- data/lib/nats/service/callbacks.rb +22 -0
- data/lib/nats/service/endpoint.rb +155 -0
- data/lib/nats/service/errors.rb +44 -0
- data/lib/nats/service/group.rb +37 -0
- data/lib/nats/service/monitoring.rb +108 -0
- data/lib/nats/service/stats.rb +52 -0
- data/lib/nats/service/status.rb +66 -0
- data/lib/nats/service/validator.rb +31 -0
- data/lib/nats/service.rb +121 -0
- data/lib/nats/utils/list.rb +26 -0
- data/lib/nats-pure.rb +5 -0
- data/lib/nats.rb +10 -6
- metadata +176 -8
data/lib/nats/io/subscription.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Copyright 2016-2021 The NATS Authors
|
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -13,45 +15,46 @@
|
|
|
13
15
|
#
|
|
14
16
|
|
|
15
17
|
module NATS
|
|
16
|
-
|
|
17
18
|
# A Subscription represents interest in a given subject.
|
|
18
|
-
#
|
|
19
|
+
#
|
|
19
20
|
# @example Create NATS subscription with callback.
|
|
20
21
|
# require 'nats/client'
|
|
21
|
-
#
|
|
22
|
+
#
|
|
22
23
|
# nc = NATS.connect("demo.nats.io")
|
|
23
24
|
# sub = nc.subscribe("foo") do |msg|
|
|
24
25
|
# puts "Received [#{msg.subject}]: #{}"
|
|
25
26
|
# end
|
|
26
|
-
#
|
|
27
|
+
#
|
|
27
28
|
class Subscription
|
|
28
29
|
include MonitorMixin
|
|
29
30
|
|
|
30
31
|
attr_accessor :subject, :queue, :future, :callback, :response, :received, :max, :pending, :sid
|
|
31
|
-
attr_accessor :pending_queue, :pending_size, :wait_for_msgs_cond
|
|
32
|
+
attr_accessor :pending_queue, :pending_size, :wait_for_msgs_cond
|
|
32
33
|
attr_accessor :pending_msgs_limit, :pending_bytes_limit
|
|
33
34
|
attr_accessor :nc
|
|
34
35
|
attr_accessor :jsi
|
|
35
|
-
attr_accessor :closed
|
|
36
|
+
attr_accessor :closed, :drained
|
|
37
|
+
alias_method :delivered, :received
|
|
36
38
|
|
|
37
39
|
def initialize(**opts)
|
|
38
40
|
super() # required to initialize monitor
|
|
39
|
-
@subject
|
|
40
|
-
@queue
|
|
41
|
-
@future
|
|
41
|
+
@subject = ""
|
|
42
|
+
@queue = nil
|
|
43
|
+
@future = nil
|
|
42
44
|
@callback = nil
|
|
43
45
|
@response = nil
|
|
44
46
|
@received = 0
|
|
45
|
-
@max
|
|
46
|
-
@pending
|
|
47
|
-
@sid
|
|
48
|
-
@nc
|
|
49
|
-
@closed
|
|
47
|
+
@max = nil
|
|
48
|
+
@pending = nil
|
|
49
|
+
@sid = nil
|
|
50
|
+
@nc = nil
|
|
51
|
+
@closed = nil
|
|
52
|
+
@drained = false
|
|
50
53
|
|
|
51
54
|
# State from async subscriber messages delivery
|
|
52
|
-
@pending_queue
|
|
53
|
-
@pending_size
|
|
54
|
-
@pending_msgs_limit
|
|
55
|
+
@pending_queue = nil
|
|
56
|
+
@pending_size = 0
|
|
57
|
+
@pending_msgs_limit = nil
|
|
55
58
|
@pending_bytes_limit = nil
|
|
56
59
|
|
|
57
60
|
# Sync subscriber
|
|
@@ -78,22 +81,22 @@ module NATS
|
|
|
78
81
|
|
|
79
82
|
# Auto unsubscribes the server by sending UNSUB command and throws away
|
|
80
83
|
# subscription in case already present and has received enough messages.
|
|
81
|
-
def unsubscribe(opt_max=nil)
|
|
84
|
+
def unsubscribe(opt_max = nil)
|
|
82
85
|
@nc.send(:unsubscribe, self, opt_max)
|
|
83
86
|
end
|
|
84
87
|
|
|
85
88
|
# next_msg blocks and waiting for the next message to be received.
|
|
86
|
-
def next_msg(opts={})
|
|
89
|
+
def next_msg(opts = {})
|
|
87
90
|
timeout = opts[:timeout] ||= 0.5
|
|
88
91
|
synchronize do
|
|
89
|
-
return @pending_queue.pop if
|
|
92
|
+
return @pending_queue.pop if !@pending_queue.empty?
|
|
90
93
|
|
|
91
94
|
# Wait for a bit until getting a signal.
|
|
92
|
-
MonotonicTime
|
|
95
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
|
93
96
|
wait_for_msgs_cond.wait(timeout)
|
|
94
97
|
end
|
|
95
98
|
|
|
96
|
-
if
|
|
99
|
+
if !@pending_queue.empty?
|
|
97
100
|
return @pending_queue.pop
|
|
98
101
|
else
|
|
99
102
|
raise NATS::Timeout
|
|
@@ -145,13 +148,20 @@ module NATS
|
|
|
145
148
|
msg = pending_queue.pop(true)
|
|
146
149
|
process(msg)
|
|
147
150
|
rescue ThreadError # queue is empty
|
|
148
|
-
|
|
151
|
+
# No release here: the ensure below releases the permit; a second
|
|
152
|
+
# release would grow the semaphore beyond processing_concurrency.
|
|
149
153
|
ensure
|
|
150
154
|
concurrency_semaphore.release
|
|
151
155
|
[concurrency_semaphore.available_permits, pending_queue.size].min.times do
|
|
152
156
|
enqueue_processing(executor)
|
|
153
157
|
end
|
|
154
158
|
end
|
|
159
|
+
rescue Concurrent::RejectedExecutionError
|
|
160
|
+
# The executor is being shut down (the connection is closing,
|
|
161
|
+
# draining or reconnecting). Release the permit so the
|
|
162
|
+
# subscription can process messages again after a reconnect;
|
|
163
|
+
# the message stays in pending_queue.
|
|
164
|
+
concurrency_semaphore.release
|
|
155
165
|
end
|
|
156
166
|
end
|
|
157
167
|
end
|
data/lib/nats/io/version.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2016-2026 The NATS Authors
|
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
5
|
# you may not use this file except in compliance with the License.
|
|
4
6
|
# You may obtain a copy of the License at
|
|
@@ -15,7 +17,7 @@
|
|
|
15
17
|
module NATS
|
|
16
18
|
module IO
|
|
17
19
|
# VERSION is the version of the client announced on CONNECT to the server.
|
|
18
|
-
VERSION = "2.
|
|
20
|
+
VERSION = "2.6.0"
|
|
19
21
|
|
|
20
22
|
# LANG is the lang runtime of the client announced on CONNECT to the server.
|
|
21
23
|
LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze
|
data/lib/nats/io/websocket.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
begin
|
|
2
|
-
require
|
|
4
|
+
require "websocket"
|
|
3
5
|
rescue LoadError
|
|
4
6
|
raise LoadError, "Please add `websocket` gem to your Gemfile to connect to NATS via WebSocket."
|
|
5
7
|
end
|
|
@@ -15,7 +17,7 @@ module NATS
|
|
|
15
17
|
|
|
16
18
|
attr_accessor :socket
|
|
17
19
|
|
|
18
|
-
def initialize(options={})
|
|
20
|
+
def initialize(options = {})
|
|
19
21
|
super
|
|
20
22
|
end
|
|
21
23
|
|
|
@@ -44,31 +46,51 @@ module NATS
|
|
|
44
46
|
super
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
def read(max_bytes=MAX_SOCKET_READ_BYTES, deadline=nil)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
result << msg
|
|
49
|
+
def read(max_bytes = MAX_SOCKET_READ_BYTES, deadline = nil)
|
|
50
|
+
# Hand over what read_line decoded beyond its last line first,
|
|
51
|
+
# otherwise those bytes are lost once the read loop takes over.
|
|
52
|
+
if @line_buf && !@line_buf.empty?
|
|
53
|
+
return @line_buf.slice!(0, @line_buf.bytesize)
|
|
53
54
|
end
|
|
54
|
-
|
|
55
|
+
|
|
56
|
+
read_frames(max_bytes, deadline)
|
|
55
57
|
end
|
|
56
58
|
|
|
57
|
-
def read_line(deadline=nil)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
def read_line(deadline = nil)
|
|
60
|
+
@line_buf ||= +""
|
|
61
|
+
loop do
|
|
62
|
+
if (idx = @line_buf =~ /\r?\n/)
|
|
63
|
+
return @line_buf.slice!(0, idx + Regexp.last_match(0).length)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Pull more data from the wire; never from @line_buf itself,
|
|
67
|
+
# or a partial line would be fed back forever.
|
|
68
|
+
data = read_frames(MAX_SOCKET_READ_BYTES, deadline)
|
|
69
|
+
return nil unless data
|
|
70
|
+
@line_buf << data
|
|
63
71
|
end
|
|
64
|
-
result.join
|
|
65
72
|
end
|
|
66
73
|
|
|
67
|
-
def write(data, deadline=nil)
|
|
74
|
+
def write(data, deadline = nil)
|
|
68
75
|
raise HandshakeError, "Attempted to write to socket while WebSocket handshake is in progress" unless @handshaked
|
|
69
76
|
|
|
70
77
|
frame = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: :binary, version: @handshake.version)
|
|
71
|
-
super
|
|
78
|
+
super(frame.to_s)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Reads from the socket and returns the payloads of all complete
|
|
84
|
+
# frames decoded so far.
|
|
85
|
+
def read_frames(max_bytes, deadline)
|
|
86
|
+
data = Socket.instance_method(:read).bind_call(self, max_bytes, deadline)
|
|
87
|
+
@frame << data
|
|
88
|
+
[].tap do |parts|
|
|
89
|
+
while (msg = @frame.next)
|
|
90
|
+
payload = msg.respond_to?(:data) ? msg.data : msg
|
|
91
|
+
parts << payload if payload
|
|
92
|
+
end
|
|
93
|
+
end.join
|
|
72
94
|
end
|
|
73
95
|
end
|
|
74
96
|
end
|
data/lib/nats/nuid.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Copyright 2016-2018 The NATS Authors
|
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -11,27 +13,27 @@
|
|
|
11
13
|
# See the License for the specific language governing permissions and
|
|
12
14
|
# limitations under the License.
|
|
13
15
|
#
|
|
14
|
-
require
|
|
16
|
+
require "securerandom"
|
|
15
17
|
|
|
16
18
|
module NATS
|
|
17
19
|
class NUID
|
|
18
|
-
DIGITS
|
|
19
|
-
BASE
|
|
20
|
+
DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".chars
|
|
21
|
+
BASE = 62
|
|
20
22
|
PREFIX_LENGTH = 12
|
|
21
|
-
SEQ_LENGTH
|
|
22
|
-
TOTAL_LENGTH
|
|
23
|
-
MAX_SEQ
|
|
24
|
-
MIN_INC
|
|
25
|
-
MAX_INC
|
|
23
|
+
SEQ_LENGTH = 10
|
|
24
|
+
TOTAL_LENGTH = PREFIX_LENGTH + SEQ_LENGTH
|
|
25
|
+
MAX_SEQ = BASE**10
|
|
26
|
+
MIN_INC = 33
|
|
27
|
+
MAX_INC = 333
|
|
26
28
|
INC = MAX_INC - MIN_INC
|
|
27
29
|
|
|
28
30
|
Ractor.make_shareable(DIGITS) if defined?(Ractor)
|
|
29
31
|
|
|
30
32
|
def initialize
|
|
31
|
-
@prand
|
|
32
|
-
@seq
|
|
33
|
-
@inc
|
|
34
|
-
@prefix
|
|
33
|
+
@prand = Random.new
|
|
34
|
+
@seq = @prand.rand(MAX_SEQ)
|
|
35
|
+
@inc = MIN_INC + @prand.rand(INC)
|
|
36
|
+
@prefix = ""
|
|
35
37
|
randomize_prefix!
|
|
36
38
|
end
|
|
37
39
|
|
|
@@ -46,22 +48,31 @@ module NATS
|
|
|
46
48
|
# Do this inline 10 times to avoid even more extra allocs,
|
|
47
49
|
# then use string interpolation of everything which works
|
|
48
50
|
# faster for doing concat.
|
|
49
|
-
s_10 = DIGITS[l % BASE]
|
|
51
|
+
s_10 = DIGITS[l % BASE]
|
|
50
52
|
|
|
51
53
|
# Ugly, but parallel assignment is slightly faster here...
|
|
52
|
-
s_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
s_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 =
|
|
55
|
+
(l /= BASE
|
|
56
|
+
DIGITS[l % BASE]), (l /= BASE
|
|
57
|
+
DIGITS[l % BASE]), (l /= BASE
|
|
58
|
+
DIGITS[l % BASE]),
|
|
59
|
+
(l /= BASE
|
|
60
|
+
DIGITS[l % BASE]), (l /= BASE
|
|
61
|
+
DIGITS[l % BASE]), (l /= BASE
|
|
62
|
+
DIGITS[l % BASE]),
|
|
63
|
+
(l /= BASE
|
|
64
|
+
DIGITS[l % BASE]), (l /= BASE
|
|
65
|
+
DIGITS[l % BASE]), (l /= BASE
|
|
66
|
+
DIGITS[l % BASE])
|
|
56
67
|
"#{@prefix}#{s_01}#{s_02}#{s_03}#{s_04}#{s_05}#{s_06}#{s_07}#{s_08}#{s_09}#{s_10}"
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
def randomize_prefix!
|
|
60
|
-
@prefix =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
@prefix =
|
|
72
|
+
SecureRandom.random_bytes(PREFIX_LENGTH).each_byte
|
|
73
|
+
.reduce("".dup) do |prefix, n|
|
|
74
|
+
prefix << DIGITS[n % BASE]
|
|
75
|
+
end
|
|
65
76
|
end
|
|
66
77
|
|
|
67
78
|
private
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NATS
|
|
4
|
+
class Service
|
|
5
|
+
class Callbacks
|
|
6
|
+
attr_reader :service, :callbacks
|
|
7
|
+
|
|
8
|
+
def initialize(service)
|
|
9
|
+
@service = service
|
|
10
|
+
@callbacks = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def register(name, &block)
|
|
14
|
+
callbacks[name] = block
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(name, *args)
|
|
18
|
+
callbacks[name]&.call(*args)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2025 The NATS Authors
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
module NATS
|
|
17
|
+
class Service
|
|
18
|
+
class Request < ::NATS::Msg
|
|
19
|
+
attr_reader :error, :endpoint
|
|
20
|
+
|
|
21
|
+
def initialize(opts = {})
|
|
22
|
+
super
|
|
23
|
+
@endpoint = opts[:endpoint]
|
|
24
|
+
@error = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def respond_with_error(error)
|
|
28
|
+
@error = NATS::Service::ErrorWrapper.new(error)
|
|
29
|
+
|
|
30
|
+
message = dup
|
|
31
|
+
message.subject = reply
|
|
32
|
+
message.reply = ""
|
|
33
|
+
message.data = @error.data
|
|
34
|
+
|
|
35
|
+
message.header = {
|
|
36
|
+
"Nats-Service-Error" => @error.message,
|
|
37
|
+
"Nats-Service-Error-Code" => @error.code
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
respond_msg(message)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def inspect
|
|
44
|
+
dot = "..." if @data.length > 10
|
|
45
|
+
dat = "#{data.slice(0, 10)}#{dot}"
|
|
46
|
+
"#<Service::Request(subject: \"#{@subject}\", reply: \"#{@reply}\", data: #{dat.inspect})>"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class << self
|
|
50
|
+
def from_msg(svc, msg)
|
|
51
|
+
request = Request.new(endpoint: svc)
|
|
52
|
+
request.subject = msg.subject
|
|
53
|
+
request.reply = msg.reply
|
|
54
|
+
request.data = msg.data
|
|
55
|
+
request.header = msg.header
|
|
56
|
+
request.nc = msg.nc
|
|
57
|
+
request.sub = msg.sub
|
|
58
|
+
|
|
59
|
+
request
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class Endpoint
|
|
65
|
+
attr_reader :name, :service, :subject, :metadata, :queue, :stats
|
|
66
|
+
|
|
67
|
+
def initialize(name:, options:, parent:, &block)
|
|
68
|
+
validate(name, options)
|
|
69
|
+
|
|
70
|
+
@name = name
|
|
71
|
+
|
|
72
|
+
@service = parent.service
|
|
73
|
+
@subject = build_subject(parent, options)
|
|
74
|
+
@queue = options[:queue] || parent.queue
|
|
75
|
+
@metadata = options[:metadata]
|
|
76
|
+
|
|
77
|
+
@stats = NATS::Service::Stats.new
|
|
78
|
+
@handler = create_handler(block)
|
|
79
|
+
|
|
80
|
+
@stopped = false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def stop
|
|
84
|
+
service.client.send(:drain_sub, @handler)
|
|
85
|
+
rescue
|
|
86
|
+
# nothing we can do here
|
|
87
|
+
ensure
|
|
88
|
+
@stopped = true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def reset
|
|
92
|
+
stats.reset
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def stopped?
|
|
96
|
+
@stopped
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def validate(name, options)
|
|
102
|
+
Validator.validate(
|
|
103
|
+
name: name,
|
|
104
|
+
subject: options[:subject],
|
|
105
|
+
queue: options[:queue]
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def build_subject(parent, options)
|
|
110
|
+
subject = options[:subject] || name
|
|
111
|
+
|
|
112
|
+
parent.subject ? "#{parent.subject}.#{subject}" : subject
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def create_handler(block)
|
|
116
|
+
service.client.subscribe(subject, queue: queue) do |msg|
|
|
117
|
+
started_at = Time.now
|
|
118
|
+
|
|
119
|
+
req = Request.from_msg(self, msg)
|
|
120
|
+
block.call(req)
|
|
121
|
+
stats.error(req.error) if req.error
|
|
122
|
+
rescue NATS::Error => error
|
|
123
|
+
stats.error(error)
|
|
124
|
+
service.stop(error)
|
|
125
|
+
|
|
126
|
+
raise error
|
|
127
|
+
rescue => error
|
|
128
|
+
stats.error(error)
|
|
129
|
+
Request.from_msg(self, msg).respond_with_error(error)
|
|
130
|
+
ensure
|
|
131
|
+
stats.record(started_at)
|
|
132
|
+
end
|
|
133
|
+
rescue => error
|
|
134
|
+
service.stop(error)
|
|
135
|
+
raise error
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class Endpoints < NATS::Utils::List
|
|
140
|
+
def add(name, options = {}, &block)
|
|
141
|
+
endpoint = Endpoint.new(
|
|
142
|
+
name: name,
|
|
143
|
+
options: options,
|
|
144
|
+
parent: parent,
|
|
145
|
+
&block
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
insert(endpoint)
|
|
149
|
+
parent.service.endpoints.insert(endpoint)
|
|
150
|
+
|
|
151
|
+
endpoint
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NATS
|
|
4
|
+
class Service
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
class InvalidNameError < Error; end
|
|
8
|
+
|
|
9
|
+
class InvalidVersionError < Error; end
|
|
10
|
+
|
|
11
|
+
class InvalidQueueError < Error; end
|
|
12
|
+
|
|
13
|
+
class InvalidSubjectError < Error; end
|
|
14
|
+
|
|
15
|
+
class ErrorWrapper
|
|
16
|
+
attr_reader :code, :message, :data
|
|
17
|
+
|
|
18
|
+
def initialize(error)
|
|
19
|
+
case error
|
|
20
|
+
when Exception
|
|
21
|
+
@code = 500
|
|
22
|
+
@message = error.message
|
|
23
|
+
@data = ""
|
|
24
|
+
when Hash
|
|
25
|
+
@code = error[:code]
|
|
26
|
+
@message = error[:description]
|
|
27
|
+
@data = error[:data]
|
|
28
|
+
when ErrorWrapper
|
|
29
|
+
@code = error.code
|
|
30
|
+
@message = error.message
|
|
31
|
+
@data = error.data
|
|
32
|
+
else
|
|
33
|
+
@code = 500
|
|
34
|
+
@message = error.to_s
|
|
35
|
+
@data = ""
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def description
|
|
40
|
+
"#{code}:#{message}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NATS
|
|
4
|
+
class Service
|
|
5
|
+
class Group
|
|
6
|
+
attr_reader :service, :name, :subject, :queue, :groups, :endpoints
|
|
7
|
+
|
|
8
|
+
def initialize(name:, parent:, queue:)
|
|
9
|
+
Validator.validate(name: name, queue: queue)
|
|
10
|
+
|
|
11
|
+
@name = name
|
|
12
|
+
|
|
13
|
+
@service = parent.service
|
|
14
|
+
@subject = parent.subject ? "#{parent.subject}.#{name}" : name
|
|
15
|
+
@queue = queue || parent.queue
|
|
16
|
+
|
|
17
|
+
@groups = Groups.new(self)
|
|
18
|
+
@endpoints = Endpoints.new(self)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Groups < NATS::Utils::List
|
|
23
|
+
def add(name, queue: nil)
|
|
24
|
+
group = Group.new(
|
|
25
|
+
name: name,
|
|
26
|
+
queue: queue,
|
|
27
|
+
parent: parent
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
insert(group)
|
|
31
|
+
parent.service.groups.insert(group)
|
|
32
|
+
|
|
33
|
+
group
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module NATS
|
|
6
|
+
class Service
|
|
7
|
+
class Monitoring
|
|
8
|
+
DEFAULT_PREFIX = "$SRV"
|
|
9
|
+
|
|
10
|
+
VERBS = {
|
|
11
|
+
ping: "PING",
|
|
12
|
+
info: "INFO",
|
|
13
|
+
stats: "STATS"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
TYPES = {
|
|
17
|
+
ping: "io.nats.micro.v1.ping_response",
|
|
18
|
+
info: "io.nats.micro.v1.info_response",
|
|
19
|
+
stats: "io.nats.micro.v1.stats_response"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
attr_reader :service, :prefix, :stopped
|
|
23
|
+
|
|
24
|
+
def initialize(service, prefix = nil)
|
|
25
|
+
@service = service
|
|
26
|
+
@prefix = prefix || DEFAULT_PREFIX
|
|
27
|
+
|
|
28
|
+
setup_monitors
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def stop
|
|
32
|
+
return if @monitors.nil?
|
|
33
|
+
|
|
34
|
+
@monitors.each do |monitor|
|
|
35
|
+
service.client.send(:drain_sub, monitor)
|
|
36
|
+
end
|
|
37
|
+
rescue
|
|
38
|
+
# nothing we can do here
|
|
39
|
+
ensure
|
|
40
|
+
@monitors = nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def stopped?
|
|
44
|
+
@monitors.nil?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def setup_monitors
|
|
50
|
+
@monitors = []
|
|
51
|
+
|
|
52
|
+
ping
|
|
53
|
+
info
|
|
54
|
+
stats
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ping
|
|
58
|
+
monitor(:ping) do
|
|
59
|
+
{
|
|
60
|
+
type: TYPES[:ping],
|
|
61
|
+
**service.status.basic
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def info
|
|
67
|
+
monitor(:info) do
|
|
68
|
+
{
|
|
69
|
+
type: TYPES[:info],
|
|
70
|
+
**service.status.info
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def stats
|
|
76
|
+
monitor(:stats) do
|
|
77
|
+
{
|
|
78
|
+
type: TYPES[:stats],
|
|
79
|
+
**service.status.stats
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def monitor(verb, &block)
|
|
85
|
+
subjects(verb).map do |subject|
|
|
86
|
+
@monitors << subscribe_monitor(subject, block)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def subscribe_monitor(subject, block)
|
|
91
|
+
service.client.subscribe(subject) do |message|
|
|
92
|
+
message.respond(block.call.to_json)
|
|
93
|
+
end
|
|
94
|
+
rescue => error
|
|
95
|
+
service.stop(error)
|
|
96
|
+
raise error
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def subjects(verb)
|
|
100
|
+
[
|
|
101
|
+
"#{prefix}.#{VERBS[verb]}",
|
|
102
|
+
"#{prefix}.#{VERBS[verb]}.#{service.name}",
|
|
103
|
+
"#{prefix}.#{VERBS[verb]}.#{service.name}.#{service.id}"
|
|
104
|
+
]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|