amqp-client 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 337f82d8ebaf862fd8fd1acd17f9b71be2aaee6de02f0c7c59786b222d5c5c0c
4
- data.tar.gz: 040adffbda90616864a845517a1904efa5c1dbb25e30e63febe3f34b3cf4f752
3
+ metadata.gz: 30006ba97b26d7e73cf9503841e2248744db3d9e86d1a32fe4c9abecd9ee69f7
4
+ data.tar.gz: a59d667d971da49c2657e57efe385853adbef958fd191a01f0d1f4dbaaa1b8a8
5
5
  SHA512:
6
- metadata.gz: 5700b885356d0c36060ffb4a9ad819bc26672a3d89fef6283cb5c4789ae3ddeafeaa57917f3001947fbdc38954eabfb138b8a98ed4a187a66912e5fb47bb3200
7
- data.tar.gz: 034c2aa88d93bf45063d41b87e92cb20545deadf7c98930a7b6626e448a84036d234274c8c1066a85e6fd35a1274dfb392128a037f6fac7a330aee0af9907a74
6
+ metadata.gz: 7ddb8f409abf9e773a6551ff53629b702e8324ea7ff794f6b14f09ef9dd7cb27a752e853bb312a8d9da88b826a7714e84006d5bb01b5efeef7bbaed56f14323a
7
+ data.tar.gz: 642e332c0a031d27d7f364b66e0ae5beefef4bad402336f24052eef2679e306eec219369d7b8b2c1d8324eabafd0c40d45aa8feb474c3cd11ddc4578203f5bea
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2021-08-20
4
+
5
+ - Channel#wait_for_confirms is a smarter way of waiting for publish confirms
6
+ - Default connection_name to $PROGRAM_NAME
7
+
8
+ ## [0.2.3] - 2021-08-19
9
+
10
+ - Improved TLS/AMQPS support
11
+
12
+ ## [0.2.2] - 2021-08-19
13
+
14
+ - TLS port issue fixed
15
+
16
+ ## [0.2.1] - 2021-08-19
17
+
18
+ - More arguments to be passed to AMQP::Client::Queue
19
+ - Can require with 'amqp-client'
3
20
 
4
21
  ## [0.2.0] - 2021-08-19
5
22
 
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "amqp/client"
data/lib/amqp/client.rb CHANGED
@@ -123,8 +123,8 @@ module AMQP
123
123
  self
124
124
  end
125
125
 
126
- def subscribe(prefetch: 1, arguments: {}, &blk)
127
- @client.subscribe(@name, prefetch: prefetch, arguments: arguments, &blk)
126
+ def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
127
+ @client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
128
128
  self
129
129
  end
130
130
 
@@ -6,15 +6,16 @@ module AMQP
6
6
  # AMQP Channel
7
7
  class Channel
8
8
  def initialize(connection, id)
9
- @replies = ::Queue.new
10
9
  @connection = connection
11
10
  @id = id
11
+ @replies = ::Queue.new
12
12
  @consumers = {}
13
- @confirm = nil
14
- @last_confirmed = 0
15
13
  @closed = nil
16
- @on_return = nil
17
14
  @open = false
15
+ @on_return = nil
16
+ @confirm = nil
17
+ @unconfirmed = ::Queue.new
18
+ @unconfirmed_empty = ::Queue.new
18
19
  end
19
20
 
20
21
  attr_reader :id, :consumers
@@ -124,15 +125,17 @@ module AMQP
124
125
  def basic_publish(body, exchange, routing_key, **properties)
125
126
  frame_max = @connection.frame_max - 8
126
127
  id = @id
128
+ mandatory = properties.delete(:mandatory) || false
127
129
 
128
130
  if 0 < body.bytesize && body.bytesize <= frame_max
129
- write_bytes FrameBytes.basic_publish(id, exchange, routing_key, properties.delete(:mandatory) || false),
131
+ write_bytes FrameBytes.basic_publish(id, exchange, routing_key, mandatory),
130
132
  FrameBytes.header(id, body.bytesize, properties),
131
133
  FrameBytes.body(id, body)
132
- return @confirm ? @confirm += 1 : nil
134
+ @unconfirmed.push @confirm += 1 if @confirm
135
+ return
133
136
  end
134
137
 
135
- write_bytes FrameBytes.basic_publish(id, exchange, routing_key, properties.delete(:mandatory) || false),
138
+ write_bytes FrameBytes.basic_publish(id, exchange, routing_key, mandatory),
136
139
  FrameBytes.header(id, body.bytesize, properties)
137
140
  pos = 0
138
141
  while pos < body.bytesize # split body into multiple frame_max frames
@@ -141,13 +144,14 @@ module AMQP
141
144
  write_bytes FrameBytes.body(id, body_part)
142
145
  pos += len
143
146
  end
144
- @confirm += 1 if @confirm
147
+ @unconfirmed.push @confirm += 1 if @confirm
148
+ nil
145
149
  end
146
150
 
147
151
  def basic_publish_confirm(body, exchange, routing_key, **properties)
148
152
  confirm_select(no_wait: true)
149
- id = basic_publish(body, exchange, routing_key, **properties)
150
- wait_for_confirm(id)
153
+ basic_publish(body, exchange, routing_key, **properties)
154
+ wait_for_confirms
151
155
  end
152
156
 
153
157
  # Consume from a queue
@@ -211,20 +215,32 @@ module AMQP
211
215
 
212
216
  write_bytes FrameBytes.confirm_select(@id, no_wait)
213
217
  expect :confirm_select_ok unless no_wait
214
- @confirms = ::Queue.new
215
218
  @confirm = 0
216
219
  end
217
220
 
218
- def wait_for_confirm(id)
219
- raise ArgumentError, "Confirm id has to a positive number" unless id&.positive?
220
- return true if @last_confirmed >= id
221
+ # Block until all publishes messages are confirmed
222
+ def wait_for_confirms
223
+ return true if @unconfirmed.empty?
224
+
225
+ @unconfirmed_empty.pop
226
+ end
221
227
 
228
+ def confirm(args)
229
+ ack_or_nack, delivery_tag, multiple = *args
222
230
  loop do
223
- ack, delivery_tag, multiple = @confirms.shift || break
224
- @last_confirmed = delivery_tag
225
- return ack if delivery_tag == id || (delivery_tag > id && multiple)
231
+ tag = @unconfirmed.pop(true)
232
+ break if tag == delivery_tag
233
+ next if multiple && tag < delivery_tag
234
+
235
+ @unconfirmed << tag # requeue
236
+ rescue ThreadError
237
+ break
238
+ end
239
+ return unless @unconfirmed.empty?
240
+
241
+ @unconfirmed_empty.num_waiting.times do
242
+ @unconfirmed_empty << ack_or_nack == :ack
226
243
  end
227
- false
228
244
  end
229
245
 
230
246
  def tx_select
@@ -246,10 +262,6 @@ module AMQP
246
262
  @replies.push(args)
247
263
  end
248
264
 
249
- def confirm(args)
250
- @confirms.push(args)
251
- end
252
-
253
265
  def message_returned(reply_code, reply_text, exchange, routing_key)
254
266
  Thread.new do
255
267
  body_size, properties = expect(:header)
@@ -15,7 +15,7 @@ module AMQP
15
15
 
16
16
  uri = URI.parse(uri)
17
17
  tls = uri.scheme == "amqps"
18
- port = port_from_env || uri.port || (@tls ? 5671 : 5672)
18
+ port = port_from_env || uri.port || (tls ? 5671 : 5672)
19
19
  host = uri.host || "localhost"
20
20
  user = uri.user || "guest"
21
21
  password = uri.password || "guest"
@@ -25,10 +25,15 @@ module AMQP
25
25
  socket = Socket.tcp host, port, connect_timeout: 20, resolv_timeout: 5
26
26
  enable_tcp_keepalive(socket)
27
27
  if tls
28
+ cert_store = OpenSSL::X509::Store.new
29
+ cert_store.set_default_paths
28
30
  context = OpenSSL::SSL::SSLContext.new
31
+ context.cert_store = cert_store
29
32
  context.verify_mode = OpenSSL::SSL::VERIFY_PEER unless [false, "false", "none"].include? options[:verify_peer]
30
33
  socket = OpenSSL::SSL::SSLSocket.new(socket, context)
31
34
  socket.sync_close = true # closing the TLS socket also closes the TCP socket
35
+ socket.hostname = host # SNI host
36
+ socket.connect
32
37
  end
33
38
  channel_max, frame_max, heartbeat = establish(socket, user, password, vhost, **options)
34
39
  Connection.new(socket, channel_max, frame_max, heartbeat, read_loop_thread: read_loop_thread)
@@ -159,7 +164,8 @@ module AMQP
159
164
  channel = @channels.delete(channel_id)
160
165
  channel.closed!(reply_code, reply_text, classid, methodid)
161
166
  when 41 # channel#close-ok
162
- @channels[channel_id].reply [:channel_close_ok]
167
+ channel = @channels.delete(channel_id)
168
+ channel.reply [:channel_close_ok]
163
169
  else raise AMQP::Client::UnsupportedMethodFrame, class_id, method_id
164
170
  end
165
171
  when 40 # exchange
@@ -258,10 +264,7 @@ module AMQP
258
264
  @channels[channel_id].reply [:basic_get_empty]
259
265
  when 80 # ack
260
266
  delivery_tag, multiple = buf.unpack("@11 Q> C")
261
- @channels[channel_id].confirm [:ack, delivery_tag, multiple]
262
- when 90 # reject
263
- delivery_tag, requeue = buf.unpack("@11 Q> C")
264
- @channels[channel_id].confirm [:reject, delivery_tag, requeue == 1]
267
+ @channels[channel_id].confirm [:ack, delivery_tag, multiple == 1]
265
268
  when 111 # recover-ok
266
269
  @channels[channel_id].reply [:basic_recover_ok]
267
270
  when 120 # nack
@@ -329,7 +332,8 @@ module AMQP
329
332
 
330
333
  case method_id
331
334
  when 10 # connection#start
332
- properties = CLIENT_PROPERTIES.merge({ connection_name: options[:connection_name] })
335
+ conn_name = options[:connection_name] || $PROGRAM_NAME
336
+ properties = CLIENT_PROPERTIES.merge({ connection_name: conn_name })
333
337
  socket.write FrameBytes.connection_start_ok "\u0000#{user}\u0000#{password}", properties
334
338
  when 30 # connection#tune
335
339
  channel_max, frame_max, heartbeat = buf.unpack("@11 S> L> S>")
@@ -69,7 +69,8 @@ module AMQP
69
69
  end
70
70
 
71
71
  if expiration
72
- expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string")
72
+ expiration = expiration.to_s if expiration.is_a?(Integer)
73
+ expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
73
74
 
74
75
  flags |= (1 << 8)
75
76
  arr << expiration.bytesize << expiration
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AMQP
4
4
  class Client
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amqp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Hörberg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-19 00:00:00.000000000 Z
11
+ date: 2021-08-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Work in progress
14
14
  email:
@@ -29,6 +29,7 @@ files:
29
29
  - amqp-client.gemspec
30
30
  - bin/console
31
31
  - bin/setup
32
+ - lib/amqp-client.rb
32
33
  - lib/amqp/client.rb
33
34
  - lib/amqp/client/channel.rb
34
35
  - lib/amqp/client/connection.rb