nats-pure 2.0.0.pre.alpha → 2.0.0.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/nats/io/client.rb +238 -77
- data/lib/nats/io/errors.rb +6 -0
- data/lib/nats/io/js.rb +186 -2
- data/lib/nats/io/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 210e29c9b6fcc68e5c89e841a068c1a389ac78a67cadcb639161d0d5c02c4bf0
|
4
|
+
data.tar.gz: 2b181886b3a96277ae494cdda48b8bf16e9721666294ea69440976b85888b40c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 910ffb697d692a3040fd6b40e835d1a2486f16525a5e814ea33dfdb2e732dd6ba3f1876b5e6dbc7dee07e9491154a6c181b41b2db08988434d9e48900ae3dcf0
|
7
|
+
data.tar.gz: a4e4d9cc9be95d1c67331576c814841b7b7784986a6ac9503ec44846e5dfbf2af978990fe482c8a9ea1064112ab54b4c65cfc619a931a20904f712d2896547a1
|
data/lib/nats/io/client.rb
CHANGED
@@ -75,6 +75,10 @@ module NATS
|
|
75
75
|
|
76
76
|
# When the client is attempting to connect to a NATS Server for the first time.
|
77
77
|
CONNECTING = 4
|
78
|
+
|
79
|
+
# When the client is draining a connection before closing.
|
80
|
+
DRAINING_SUBS = 5
|
81
|
+
DRAINING_PUBS = 6
|
78
82
|
end
|
79
83
|
|
80
84
|
# Client creates a connection to the NATS Server.
|
@@ -182,6 +186,14 @@ module NATS
|
|
182
186
|
@user_nkey_cb = nil
|
183
187
|
@user_jwt_cb = nil
|
184
188
|
@signature_cb = nil
|
189
|
+
|
190
|
+
# Tokens
|
191
|
+
@auth_token = nil
|
192
|
+
|
193
|
+
@inbox_prefix = "_INBOX"
|
194
|
+
|
195
|
+
# Draining
|
196
|
+
@drain_t = nil
|
185
197
|
end
|
186
198
|
|
187
199
|
# Establishes a connection to NATS.
|
@@ -229,6 +241,7 @@ module NATS
|
|
229
241
|
opts[:ping_interval] = ENV['NATS_PING_INTERVAL'].to_i unless ENV['NATS_PING_INTERVAL'].nil?
|
230
242
|
opts[:max_outstanding_pings] = ENV['NATS_MAX_OUTSTANDING_PINGS'].to_i unless ENV['NATS_MAX_OUTSTANDING_PINGS'].nil?
|
231
243
|
opts[:connect_timeout] ||= NATS::IO::DEFAULT_CONNECT_TIMEOUT
|
244
|
+
opts[:drain_timeout] ||= NATS::IO::DEFAULT_DRAIN_TIMEOUT
|
232
245
|
@options = opts
|
233
246
|
|
234
247
|
# Process servers in the NATS cluster and pick one to connect
|
@@ -254,13 +267,24 @@ module NATS
|
|
254
267
|
end
|
255
268
|
|
256
269
|
# NKEYS
|
270
|
+
@signature_cb ||= opts[:user_signature_cb]
|
271
|
+
@user_jwt_cb ||= opts[:user_jwt_cb]
|
272
|
+
@user_nkey_cb ||= opts[:user_nkey_cb]
|
257
273
|
@user_credentials ||= opts[:user_credentials]
|
258
274
|
@nkeys_seed ||= opts[:nkeys_seed]
|
275
|
+
|
259
276
|
setup_nkeys_connect if @user_credentials or @nkeys_seed
|
260
277
|
|
278
|
+
# Tokens, if set will take preference over the user@server uri token
|
279
|
+
@auth_token ||= opts[:auth_token]
|
280
|
+
|
261
281
|
# Check for TLS usage
|
262
282
|
@tls = @options[:tls]
|
263
283
|
|
284
|
+
@inbox_prefix = opts.fetch(:custom_inbox_prefix, @inbox_prefix)
|
285
|
+
|
286
|
+
validate_settings!
|
287
|
+
|
264
288
|
srv = nil
|
265
289
|
begin
|
266
290
|
srv = select_next_server
|
@@ -387,6 +411,8 @@ module NATS
|
|
387
411
|
# Create subscription which is dispatched asynchronously
|
388
412
|
# messages to a callback.
|
389
413
|
def subscribe(subject, opts={}, &callback)
|
414
|
+
raise NATS::IO::ConnectionDrainingError.new("nats: connection draining") if draining?
|
415
|
+
|
390
416
|
sid = nil
|
391
417
|
sub = nil
|
392
418
|
synchronize do
|
@@ -640,7 +666,7 @@ module NATS
|
|
640
666
|
end
|
641
667
|
|
642
668
|
# Send a ping and wait for a pong back within a timeout.
|
643
|
-
def flush(timeout=
|
669
|
+
def flush(timeout=10)
|
644
670
|
# Schedule sending a PING, and block until we receive PONG back,
|
645
671
|
# or raise a timeout in case the response is past the deadline.
|
646
672
|
pong = @pongs.new_cond
|
@@ -674,7 +700,7 @@ module NATS
|
|
674
700
|
# new_inbox returns a unique inbox used for subscriptions.
|
675
701
|
# @return [String]
|
676
702
|
def new_inbox
|
677
|
-
"
|
703
|
+
"#{@inbox_prefix}.#{@nuid.next}"
|
678
704
|
end
|
679
705
|
|
680
706
|
def connected_server
|
@@ -697,6 +723,19 @@ module NATS
|
|
697
723
|
@status == CLOSED
|
698
724
|
end
|
699
725
|
|
726
|
+
def draining?
|
727
|
+
if @status == DRAINING_PUBS or @status == DRAINING_SUBS
|
728
|
+
return true
|
729
|
+
end
|
730
|
+
|
731
|
+
is_draining = false
|
732
|
+
synchronize do
|
733
|
+
is_draining = true if @drain_t
|
734
|
+
end
|
735
|
+
|
736
|
+
is_draining
|
737
|
+
end
|
738
|
+
|
700
739
|
def on_error(&callback)
|
701
740
|
@err_cb = callback
|
702
741
|
end
|
@@ -719,6 +758,19 @@ module NATS
|
|
719
758
|
end
|
720
759
|
end
|
721
760
|
|
761
|
+
# drain will put a connection into a drain state. All subscriptions will
|
762
|
+
# immediately be put into a drain state. Upon completion, the publishers
|
763
|
+
# will be drained and can not publish any additional messages. Upon draining
|
764
|
+
# of the publishers, the connection will be closed. Use the `on_close`
|
765
|
+
# callback option to know when the connection has moved from draining to closed.
|
766
|
+
def drain
|
767
|
+
return if draining?
|
768
|
+
|
769
|
+
synchronize do
|
770
|
+
@drain_t ||= Thread.new { do_drain }
|
771
|
+
end
|
772
|
+
end
|
773
|
+
|
722
774
|
# Create a JetStream context.
|
723
775
|
# @param opts [Hash] Options to customize the JetStream context.
|
724
776
|
# @option params [String] :prefix JetStream API prefix to use for the requests.
|
@@ -733,6 +785,13 @@ module NATS
|
|
733
785
|
|
734
786
|
private
|
735
787
|
|
788
|
+
def validate_settings!
|
789
|
+
raise(NATS::IO::ClientError, "custom inbox may not include '>'") if @inbox_prefix.include?(">")
|
790
|
+
raise(NATS::IO::ClientError, "custom inbox may not include '*'") if @inbox_prefix.include?("*")
|
791
|
+
raise(NATS::IO::ClientError, "custom inbox may not end in '.'") if @inbox_prefix.end_with?(".")
|
792
|
+
raise(NATS::IO::ClientError, "custom inbox may not begin with '.'") if @inbox_prefix.start_with?(".")
|
793
|
+
end
|
794
|
+
|
736
795
|
def process_info(line)
|
737
796
|
parsed_info = JSON.parse(line)
|
738
797
|
|
@@ -995,6 +1054,83 @@ module NATS
|
|
995
1054
|
end
|
996
1055
|
end
|
997
1056
|
|
1057
|
+
def drain_sub(sub)
|
1058
|
+
sid = nil
|
1059
|
+
closed = nil
|
1060
|
+
sub.synchronize do
|
1061
|
+
sid = sub.sid
|
1062
|
+
closed = sub.closed
|
1063
|
+
end
|
1064
|
+
return if closed
|
1065
|
+
|
1066
|
+
send_command("UNSUB #{sid}#{CR_LF}")
|
1067
|
+
@flush_queue << :drain
|
1068
|
+
|
1069
|
+
synchronize { sub = @subs[sid] }
|
1070
|
+
return unless sub
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
def do_drain
|
1074
|
+
synchronize { @status = DRAINING_SUBS }
|
1075
|
+
|
1076
|
+
# Do unsubscribe protocol for all the susbcriptions, then have a single thread
|
1077
|
+
# waiting until all subs are done or drain timeout error reported to async error cb.
|
1078
|
+
subs = []
|
1079
|
+
@subs.each do |_, sub|
|
1080
|
+
next if sub == @resp_sub
|
1081
|
+
drain_sub(sub)
|
1082
|
+
subs << sub
|
1083
|
+
end
|
1084
|
+
force_flush!
|
1085
|
+
|
1086
|
+
# Wait until all subs have no pending messages.
|
1087
|
+
drain_timeout = MonotonicTime::now + @options[:drain_timeout]
|
1088
|
+
to_delete = []
|
1089
|
+
|
1090
|
+
loop do
|
1091
|
+
break if MonotonicTime::now > drain_timeout
|
1092
|
+
sleep 0.1
|
1093
|
+
|
1094
|
+
# Wait until all subs are done.
|
1095
|
+
@subs.each do |_, sub|
|
1096
|
+
if sub != @resp_sub and sub.pending_queue.size == 0
|
1097
|
+
to_delete << sub
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
next if to_delete.empty?
|
1101
|
+
|
1102
|
+
to_delete.each do |sub|
|
1103
|
+
@subs.delete(sub.sid)
|
1104
|
+
# Stop messages delivery thread for async subscribers
|
1105
|
+
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
1106
|
+
sub.wait_for_msgs_t.exit
|
1107
|
+
sub.pending_queue.clear
|
1108
|
+
end
|
1109
|
+
end
|
1110
|
+
to_delete.clear
|
1111
|
+
|
1112
|
+
# Wait until only the resp mux is remaining or there are no subscriptions.
|
1113
|
+
if @subs.count == 1
|
1114
|
+
sid, sub = @subs.first
|
1115
|
+
if sub == @resp_sub
|
1116
|
+
break
|
1117
|
+
end
|
1118
|
+
elsif @subs.count == 0
|
1119
|
+
break
|
1120
|
+
end
|
1121
|
+
end
|
1122
|
+
|
1123
|
+
if MonotonicTime::now > drain_timeout
|
1124
|
+
e = NATS::IO::DrainTimeoutError.new("nats: draining connection timed out")
|
1125
|
+
err_cb_call(self, e, nil) if @err_cb
|
1126
|
+
end
|
1127
|
+
synchronize { @status = DRAINING_PUBS }
|
1128
|
+
|
1129
|
+
# Remove resp mux handler in case there is one.
|
1130
|
+
unsubscribe(@resp_sub) if @resp_sub
|
1131
|
+
close
|
1132
|
+
end
|
1133
|
+
|
998
1134
|
def send_flush_queue(s)
|
999
1135
|
@flush_queue << s
|
1000
1136
|
end
|
@@ -1037,16 +1173,18 @@ module NATS
|
|
1037
1173
|
else
|
1038
1174
|
cs[:auth_token] = @uri.user
|
1039
1175
|
end
|
1040
|
-
when @
|
1176
|
+
when @user_jwt_cb && @signature_cb
|
1041
1177
|
nonce = @server_info[:nonce]
|
1042
1178
|
cs[:jwt] = @user_jwt_cb.call
|
1043
1179
|
cs[:sig] = @signature_cb.call(nonce)
|
1044
|
-
when @
|
1180
|
+
when @user_nkey_cb && @signature_cb
|
1045
1181
|
nonce = @server_info[:nonce]
|
1046
1182
|
cs[:nkey] = @user_nkey_cb.call
|
1047
1183
|
cs[:sig] = @signature_cb.call(nonce)
|
1048
1184
|
end
|
1049
1185
|
|
1186
|
+
cs[:auth_token] = @auth_token if @auth_token
|
1187
|
+
|
1050
1188
|
if @server_info[:headers]
|
1051
1189
|
cs[:headers] = @server_info[:headers]
|
1052
1190
|
cs[:no_responders] = if @options[:no_responders] == false
|
@@ -1150,22 +1288,7 @@ module NATS
|
|
1150
1288
|
# Skip in case nothing remains pending already.
|
1151
1289
|
next if @pending_queue.empty?
|
1152
1290
|
|
1153
|
-
|
1154
|
-
# since producers could be adding as many as possible
|
1155
|
-
# until reaching the max pending queue size.
|
1156
|
-
cmds = []
|
1157
|
-
cmds << @pending_queue.pop until @pending_queue.empty?
|
1158
|
-
begin
|
1159
|
-
@io.write(cmds.join) unless cmds.empty?
|
1160
|
-
rescue => e
|
1161
|
-
synchronize do
|
1162
|
-
@last_err = e
|
1163
|
-
err_cb_call(self, e, nil) if @err_cb
|
1164
|
-
end
|
1165
|
-
|
1166
|
-
process_op_error(e)
|
1167
|
-
return
|
1168
|
-
end if @io
|
1291
|
+
force_flush!
|
1169
1292
|
|
1170
1293
|
synchronize do
|
1171
1294
|
@pending_size = 0
|
@@ -1173,6 +1296,25 @@ module NATS
|
|
1173
1296
|
end
|
1174
1297
|
end
|
1175
1298
|
|
1299
|
+
def force_flush!
|
1300
|
+
# FIXME: should limit how many commands to take at once
|
1301
|
+
# since producers could be adding as many as possible
|
1302
|
+
# until reaching the max pending queue size.
|
1303
|
+
cmds = []
|
1304
|
+
cmds << @pending_queue.pop until @pending_queue.empty?
|
1305
|
+
begin
|
1306
|
+
@io.write(cmds.join) unless cmds.empty?
|
1307
|
+
rescue => e
|
1308
|
+
synchronize do
|
1309
|
+
@last_err = e
|
1310
|
+
err_cb_call(self, e, nil) if @err_cb
|
1311
|
+
end
|
1312
|
+
|
1313
|
+
process_op_error(e)
|
1314
|
+
return
|
1315
|
+
end if @io
|
1316
|
+
end
|
1317
|
+
|
1176
1318
|
def ping_interval_loop
|
1177
1319
|
loop do
|
1178
1320
|
sleep @options[:ping_interval]
|
@@ -1446,7 +1588,7 @@ module NATS
|
|
1446
1588
|
# Prepares requests subscription that handles the responses
|
1447
1589
|
# for the new style request response.
|
1448
1590
|
def start_resp_mux_sub!
|
1449
|
-
@resp_sub_prefix =
|
1591
|
+
@resp_sub_prefix = new_inbox
|
1450
1592
|
@resp_map = Hash.new { |h,k| h[k] = { }}
|
1451
1593
|
|
1452
1594
|
@resp_sub = Subscription.new
|
@@ -1529,72 +1671,90 @@ module NATS
|
|
1529
1671
|
|
1530
1672
|
case
|
1531
1673
|
when @nkeys_seed
|
1532
|
-
@user_nkey_cb =
|
1533
|
-
|
1534
|
-
|
1674
|
+
@user_nkey_cb = nkey_cb_for_nkey_file(@nkeys_seed)
|
1675
|
+
@signature_cb = signature_cb_for_nkey_file(@nkeys_seed)
|
1676
|
+
when @user_credentials
|
1677
|
+
# When the credentials are within a single decorated file.
|
1678
|
+
@user_jwt_cb = jwt_cb_for_creds_file(@user_credentials)
|
1679
|
+
@signature_cb = signature_cb_for_creds_file(@user_credentials)
|
1680
|
+
end
|
1681
|
+
end
|
1535
1682
|
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1683
|
+
def signature_cb_for_nkey_file(nkey)
|
1684
|
+
proc { |nonce|
|
1685
|
+
seed = File.read(nkey).chomp
|
1686
|
+
kp = NKEYS::from_seed(seed)
|
1687
|
+
raw_signed = kp.sign(nonce)
|
1688
|
+
kp.wipe!
|
1689
|
+
encoded = Base64.urlsafe_encode64(raw_signed)
|
1690
|
+
encoded.gsub('=', '')
|
1691
|
+
}
|
1692
|
+
end
|
1539
1693
|
|
1540
|
-
|
1541
|
-
|
1694
|
+
def nkey_cb_for_nkey_file(nkey)
|
1695
|
+
proc {
|
1696
|
+
seed = File.read(nkey).chomp
|
1697
|
+
kp = NKEYS::from_seed(seed)
|
1542
1698
|
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
end
|
1699
|
+
# Take a copy since original will be gone with the wipe.
|
1700
|
+
pub_key = kp.public_key.dup
|
1701
|
+
kp.wipe!
|
1702
|
+
|
1703
|
+
pub_key
|
1704
|
+
}
|
1705
|
+
end
|
1706
|
+
|
1707
|
+
def jwt_cb_for_creds_file(creds)
|
1708
|
+
proc {
|
1709
|
+
jwt_start = "BEGIN NATS USER JWT".freeze
|
1710
|
+
found = false
|
1711
|
+
jwt = nil
|
1712
|
+
|
1713
|
+
File.readlines(creds).each do |line|
|
1714
|
+
case
|
1715
|
+
when found
|
1716
|
+
jwt = line.chomp
|
1717
|
+
break
|
1718
|
+
when line.include?(jwt_start)
|
1719
|
+
found = true
|
1565
1720
|
end
|
1566
|
-
|
1721
|
+
end
|
1567
1722
|
|
1568
|
-
|
1569
|
-
}
|
1723
|
+
raise(Error, "No JWT found in #{creds}") if not found
|
1570
1724
|
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1725
|
+
jwt
|
1726
|
+
}
|
1727
|
+
end
|
1728
|
+
|
1729
|
+
def signature_cb_for_creds_file(creds)
|
1730
|
+
proc { |nonce|
|
1731
|
+
seed_start = "BEGIN USER NKEY SEED".freeze
|
1732
|
+
found = false
|
1733
|
+
seed = nil
|
1734
|
+
|
1735
|
+
File.readlines(creds).each do |line|
|
1736
|
+
case
|
1737
|
+
when found
|
1738
|
+
seed = line.chomp
|
1739
|
+
break
|
1740
|
+
when line.include?(seed_start)
|
1741
|
+
found = true
|
1583
1742
|
end
|
1584
|
-
|
1743
|
+
end
|
1585
1744
|
|
1586
|
-
|
1587
|
-
raw_signed = kp.sign(nonce)
|
1745
|
+
raise(Error, "No nkey user seed found in #{creds}") if not found
|
1588
1746
|
|
1589
|
-
|
1590
|
-
|
1591
|
-
kp.wipe
|
1592
|
-
encoded = Base64.urlsafe_encode64(raw_signed)
|
1747
|
+
kp = NKEYS::from_seed(seed)
|
1748
|
+
raw_signed = kp.sign(nonce)
|
1593
1749
|
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1750
|
+
# seed is a reference so also cleared when doing wipe,
|
1751
|
+
# which can be done since Ruby strings are mutable.
|
1752
|
+
kp.wipe
|
1753
|
+
encoded = Base64.urlsafe_encode64(raw_signed)
|
1754
|
+
|
1755
|
+
# Remove padding
|
1756
|
+
encoded.gsub('=', '')
|
1757
|
+
}
|
1598
1758
|
end
|
1599
1759
|
|
1600
1760
|
def process_uri(uris)
|
@@ -1655,6 +1815,7 @@ module NATS
|
|
1655
1815
|
# Default IO timeouts
|
1656
1816
|
DEFAULT_CONNECT_TIMEOUT = 2
|
1657
1817
|
DEFAULT_READ_WRITE_TIMEOUT = 2
|
1818
|
+
DEFAULT_DRAIN_TIMEOUT = 30
|
1658
1819
|
|
1659
1820
|
# Default Pending Limits
|
1660
1821
|
DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536
|
data/lib/nats/io/errors.rb
CHANGED
@@ -52,6 +52,12 @@ module NATS
|
|
52
52
|
|
53
53
|
# When a subscription hits the pending messages limit.
|
54
54
|
class SlowConsumer < Error; end
|
55
|
+
|
56
|
+
# When an action cannot be done because client is draining.
|
57
|
+
class ConnectionDrainingError < Error; end
|
58
|
+
|
59
|
+
# When drain takes too long to complete.
|
60
|
+
class DrainTimeoutError < Error; end
|
55
61
|
end
|
56
62
|
|
57
63
|
# Timeout is raised when the client gives up waiting for a response from a service.
|
data/lib/nats/io/js.rb
CHANGED
@@ -14,7 +14,9 @@
|
|
14
14
|
require_relative 'msg'
|
15
15
|
require_relative 'client'
|
16
16
|
require_relative 'errors'
|
17
|
+
require_relative 'kv'
|
17
18
|
require 'time'
|
19
|
+
require 'base64'
|
18
20
|
|
19
21
|
module NATS
|
20
22
|
|
@@ -49,6 +51,7 @@ module NATS
|
|
49
51
|
|
50
52
|
# Include JetStream::Manager
|
51
53
|
extend Manager
|
54
|
+
extend KeyValue::Manager
|
52
55
|
end
|
53
56
|
|
54
57
|
# PubAck is the API response from a successfully published message.
|
@@ -96,6 +99,119 @@ module NATS
|
|
96
99
|
PubAck.new(result)
|
97
100
|
end
|
98
101
|
|
102
|
+
# subscribe binds or creates a push subscription to a JetStream pull consumer.
|
103
|
+
#
|
104
|
+
# @param subject [String] Subject from which the messages will be fetched.
|
105
|
+
# @param params [Hash] Options to customize the PushSubscription.
|
106
|
+
# @option params [String] :stream Name of the Stream to which the consumer belongs.
|
107
|
+
# @option params [String] :consumer Name of the Consumer to which the PushSubscription will be bound.
|
108
|
+
# @option params [String] :durable Consumer durable name from where the messages will be fetched.
|
109
|
+
# @option params [Hash] :config Configuration for the consumer.
|
110
|
+
# @return [NATS::JetStream::PushSubscription]
|
111
|
+
def subscribe(subject, params={}, &cb)
|
112
|
+
params[:consumer] ||= params[:durable]
|
113
|
+
stream = params[:stream].nil? ? find_stream_name_by_subject(subject) : params[:stream]
|
114
|
+
|
115
|
+
queue = params[:queue]
|
116
|
+
durable = params[:durable]
|
117
|
+
flow_control = params[:flow_control]
|
118
|
+
manual_ack = params[:manual_ack]
|
119
|
+
idle_heartbeat = params[:idle_heartbeat]
|
120
|
+
flow_control = params[:flow_control]
|
121
|
+
|
122
|
+
if queue
|
123
|
+
if durable and durable != queue
|
124
|
+
raise NATS::JetStream::Error.new("nats: cannot create queue subscription '#{queue}' to consumer '#{durable}'")
|
125
|
+
else
|
126
|
+
durable = queue
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
cinfo = nil
|
131
|
+
consumer_found = false
|
132
|
+
should_create = false
|
133
|
+
|
134
|
+
if not durable
|
135
|
+
should_create = true
|
136
|
+
else
|
137
|
+
begin
|
138
|
+
cinfo = consumer_info(stream, durable)
|
139
|
+
config = cinfo.config
|
140
|
+
consumer_found = true
|
141
|
+
consumer = durable
|
142
|
+
rescue NATS::JetStream::Error::NotFound
|
143
|
+
should_create = true
|
144
|
+
consumer_found = false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
if consumer_found
|
149
|
+
if not config.deliver_group
|
150
|
+
if queue
|
151
|
+
raise NATS::JetStream::Error.new("nats: cannot create a queue subscription for a consumer without a deliver group")
|
152
|
+
elsif cinfo.push_bound
|
153
|
+
raise NATS::JetStream::Error.new("nats: consumer is already bound to a subscription")
|
154
|
+
end
|
155
|
+
else
|
156
|
+
if not queue
|
157
|
+
raise NATS::JetStream::Error.new("nats: cannot create a subscription for a consumer with a deliver group #{config.deliver_group}")
|
158
|
+
elsif queue != config.deliver_group
|
159
|
+
raise NATS::JetStream::Error.new("nats: cannot create a queue subscription #{queue} for a consumer with a deliver group #{config.deliver_group}")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
elsif should_create
|
163
|
+
# Auto-create consumer if none found.
|
164
|
+
if config.nil?
|
165
|
+
# Defaults
|
166
|
+
config = JetStream::API::ConsumerConfig.new({ack_policy: "explicit"})
|
167
|
+
elsif config.is_a?(Hash)
|
168
|
+
config = JetStream::API::ConsumerConfig.new(config)
|
169
|
+
elsif !config.is_a?(JetStream::API::ConsumerConfig)
|
170
|
+
raise NATS::JetStream::Error.new("nats: invalid ConsumerConfig")
|
171
|
+
end
|
172
|
+
|
173
|
+
config.durable_name = durable if not config.durable_name
|
174
|
+
config.deliver_group = queue if not config.deliver_group
|
175
|
+
|
176
|
+
# Create inbox for push consumer.
|
177
|
+
deliver = @nc.new_inbox
|
178
|
+
config.deliver_subject = deliver
|
179
|
+
|
180
|
+
# Auto created consumers use the filter subject.
|
181
|
+
config.filter_subject = subject
|
182
|
+
|
183
|
+
# Heartbeats / FlowControl
|
184
|
+
config.flow_control = flow_control
|
185
|
+
if idle_heartbeat or config.idle_heartbeat
|
186
|
+
idle_heartbeat = config.idle_heartbeat if config.idle_heartbeat
|
187
|
+
idle_heartbeat = idle_heartbeat * 1_000_000_000
|
188
|
+
config.idle_heartbeat = idle_heartbeat
|
189
|
+
end
|
190
|
+
|
191
|
+
# Auto create the consumer.
|
192
|
+
cinfo = add_consumer(stream, config)
|
193
|
+
consumer = cinfo.name
|
194
|
+
end
|
195
|
+
|
196
|
+
# Enable auto acking for async callbacks unless disabled.
|
197
|
+
if cb and not manual_ack
|
198
|
+
ocb = cb
|
199
|
+
new_cb = proc do |msg|
|
200
|
+
ocb.call(msg)
|
201
|
+
msg.ack rescue JetStream::Error::MsgAlreadyAckd
|
202
|
+
end
|
203
|
+
cb = new_cb
|
204
|
+
end
|
205
|
+
sub = @nc.subscribe(config.deliver_subject, queue: config.deliver_group, &cb)
|
206
|
+
sub.extend(PushSubscription)
|
207
|
+
sub.jsi = JS::Sub.new(
|
208
|
+
js: self,
|
209
|
+
stream: stream,
|
210
|
+
consumer: consumer,
|
211
|
+
)
|
212
|
+
sub
|
213
|
+
end
|
214
|
+
|
99
215
|
# pull_subscribe binds or creates a subscription to a JetStream pull consumer.
|
100
216
|
#
|
101
217
|
# @param subject [String] Subject from which the messages will be fetched.
|
@@ -103,6 +219,7 @@ module NATS
|
|
103
219
|
# @param params [Hash] Options to customize the PullSubscription.
|
104
220
|
# @option params [String] :stream Name of the Stream to which the consumer belongs.
|
105
221
|
# @option params [String] :consumer Name of the Consumer to which the PullSubscription will be bound.
|
222
|
+
# @option params [Hash] :config Configuration for the consumer.
|
106
223
|
# @return [NATS::JetStream::PullSubscription]
|
107
224
|
def pull_subscribe(subject, durable, params={})
|
108
225
|
raise JetStream::Error::InvalidDurableName.new("nats: invalid durable name") if durable.empty?
|
@@ -277,6 +394,14 @@ module NATS
|
|
277
394
|
result[:streams].first
|
278
395
|
end
|
279
396
|
|
397
|
+
def get_last_msg(stream_name, subject)
|
398
|
+
req_subject = "#{@prefix}.STREAM.MSG.GET.#{stream_name}"
|
399
|
+
req = {'last_by_subj': subject}
|
400
|
+
data = req.to_json
|
401
|
+
resp = api_request(req_subject, data)
|
402
|
+
JetStream::API::RawStreamMsg.new(resp[:message])
|
403
|
+
end
|
404
|
+
|
280
405
|
private
|
281
406
|
|
282
407
|
def api_request(req_subject, req="", params={})
|
@@ -293,6 +418,31 @@ module NATS
|
|
293
418
|
end
|
294
419
|
end
|
295
420
|
|
421
|
+
# PushSubscription is included into NATS::Subscription so that it
|
422
|
+
#
|
423
|
+
# @example Create a push subscription using JetStream context.
|
424
|
+
#
|
425
|
+
# require 'nats/client'
|
426
|
+
#
|
427
|
+
# nc = NATS.connect
|
428
|
+
# js = nc.jetstream
|
429
|
+
# sub = js.subscribe("foo", "bar")
|
430
|
+
# msg = sub.next_msg
|
431
|
+
# msg.ack
|
432
|
+
# sub.unsubscribe
|
433
|
+
#
|
434
|
+
# @!visibility public
|
435
|
+
module PushSubscription
|
436
|
+
# consumer_info retrieves the current status of the pull subscription consumer.
|
437
|
+
# @param params [Hash] Options to customize API request.
|
438
|
+
# @option params [Float] :timeout Time to wait for response.
|
439
|
+
# @return [JetStream::API::ConsumerInfo] The latest ConsumerInfo of the consumer.
|
440
|
+
def consumer_info(params={})
|
441
|
+
@jsi.js.consumer_info(@jsi.stream, @jsi.consumer, params)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
private_constant :PushSubscription
|
445
|
+
|
296
446
|
# PullSubscription is included into NATS::Subscription so that it
|
297
447
|
# can be used to fetch messages from a pull based consumer from
|
298
448
|
# JetStream.
|
@@ -617,7 +767,11 @@ module NATS
|
|
617
767
|
private
|
618
768
|
|
619
769
|
def ensure_is_acked_once!
|
620
|
-
@sub.synchronize
|
770
|
+
@sub.synchronize do
|
771
|
+
if @ackd
|
772
|
+
raise JetStream::Error::MsgAlreadyAckd.new("nats: message was already acknowledged: #{self}")
|
773
|
+
end
|
774
|
+
end
|
621
775
|
end
|
622
776
|
|
623
777
|
def parse_metadata(reply)
|
@@ -771,9 +925,12 @@ module NATS
|
|
771
925
|
# When an invalid durable or consumer name was attempted to be used.
|
772
926
|
class InvalidDurableName < Error; end
|
773
927
|
|
774
|
-
# When an ack
|
928
|
+
# When an ack not longer valid.
|
775
929
|
class InvalidJSAck < Error; end
|
776
930
|
|
931
|
+
# When an ack has already been acked.
|
932
|
+
class MsgAlreadyAckd < Error; end
|
933
|
+
|
777
934
|
# When the delivered message does not behave as a message delivered by JetStream,
|
778
935
|
# for example when the ack reply has unrecognizable fields.
|
779
936
|
class NotJSMessage < Error; end
|
@@ -1085,6 +1242,33 @@ module NATS
|
|
1085
1242
|
freeze
|
1086
1243
|
end
|
1087
1244
|
end
|
1245
|
+
|
1246
|
+
RawStreamMsg = Struct.new(:subject, :seq, :data, :headers, keyword_init: true) do
|
1247
|
+
def initialize(opts)
|
1248
|
+
opts[:data] = Base64.decode64(opts[:data]) if opts[:data]
|
1249
|
+
if opts[:hdrs]
|
1250
|
+
header = Base64.decode64(opts[:hdrs])
|
1251
|
+
hdr = {}
|
1252
|
+
lines = header.lines
|
1253
|
+
lines.slice(1, header.size).each do |line|
|
1254
|
+
line.rstrip!
|
1255
|
+
next if line.empty?
|
1256
|
+
key, value = line.strip.split(/\s*:\s*/, 2)
|
1257
|
+
hdr[key] = value
|
1258
|
+
end
|
1259
|
+
opts[:headers] = hdr
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
# Filter out members not present.
|
1263
|
+
rem = opts.keys - members
|
1264
|
+
opts.delete_if { |k| rem.include?(k) }
|
1265
|
+
super(opts)
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
def sequence
|
1269
|
+
self.seq
|
1270
|
+
end
|
1271
|
+
end
|
1088
1272
|
end
|
1089
1273
|
end
|
1090
1274
|
end
|
data/lib/nats/io/version.rb
CHANGED
@@ -15,7 +15,7 @@
|
|
15
15
|
module NATS
|
16
16
|
module IO
|
17
17
|
# VERSION is the version of the client announced on CONNECT to the server.
|
18
|
-
VERSION = "2.0.0-
|
18
|
+
VERSION = "2.0.0-rc1".freeze
|
19
19
|
|
20
20
|
# LANG is the lang runtime of the client announced on CONNECT to the server.
|
21
21
|
LANG = "#{RUBY_ENGINE}#{RUBY_VERSION}".freeze
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nats-pure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.pre.
|
4
|
+
version: 2.0.0.pre.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Waldemar Quevedo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: NATS is an open-source, high-performance, lightweight cloud messaging
|
14
14
|
system.
|