twingly-amqp 4.3.0 → 5.2.0

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
- SHA1:
3
- metadata.gz: 4db0f7716677c33ee82f1fc7b0530aa99a9c704b
4
- data.tar.gz: a392bce55f82c6f07c39a6df3788bc8c918af654
2
+ SHA256:
3
+ metadata.gz: 9addd8c457e9a56236f8e77d5c8a695859b39c8e2f77660e5cea41d66b515095
4
+ data.tar.gz: e9c37097b065eeb9a60f25f659597975452a7b26c643d8fd81bd165bf2b6fa0a
5
5
  SHA512:
6
- metadata.gz: e8831829877ca9fa5ac54f2ab7cba84f91b33b3e7fe280c0c47b214dcf7365b15b80e37fc330fe5c3433de5e4ab5cf6808802ea272b79d71e54d9997fe139d21
7
- data.tar.gz: 83cb819bd56f5a9737129f00bb406d977aa3386a10c0a620188b9f7521dcf7463e535db38b95aa0e06cd227d91264164ce55d9711484d219afcfce13a4804d9d
6
+ metadata.gz: eebc4870eeca8f9c58298b5f839569338da8e1e862b785512babd5f7fa28492a40a069762c136d65d4c31142d1360b27cf23e5b1044dc3b4f9feac8b1ace623e
7
+ data.tar.gz: bfbd5d0598e5ba0abbf2d292ef4bee8847ee67dfd1e2d67f9a5c4b8a64a2e2131aa73c91267b9088ee5fa36162f3e7138be0b22c8c081205f290ca8de68cb035
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Twingly::AMQP
2
2
 
3
- [![Build Status](https://travis-ci.org/twingly/twingly-amqp.svg?branch=master)](https://travis-ci.org/twingly/twingly-amqp)
4
-
3
+ [![GitHub Build Status](https://github.com/twingly/twingly-amqp/workflows/CI/badge.svg?branch=master)](https://github.com/twingly/twingly-amqp/actions)
5
4
 
6
5
  A gem for subscribing and publishing messages via RabbitMQ.
7
6
 
@@ -55,11 +54,11 @@ end
55
54
  ```ruby
56
55
  subscription = Twingly::AMQP::Subscription.new(
57
56
  queue_name: "crawler-urls",
58
- exchange_topic: "url-exchange", # Optional, uses the default exchange if omitted
59
- routing_key: "url.blog", # Optional, uses the default exchange if omitted
60
- consumer_threads: 4, # Optional
61
- prefetch: 20, # Optional
62
- max_length: 10_000, # Optional
57
+ exchange_topic: "url-exchange", # Optional, uses the default exchange if omitted
58
+ routing_keys: %w(url.blog url.post), # Optional, uses the default exchange if omitted
59
+ consumer_threads: 4, # Optional
60
+ prefetch: 20, # Optional
61
+ max_length: 10_000, # Optional
63
62
  )
64
63
 
65
64
  subscription.on_exception { |exception| puts "Oh noes! #{exception.message}" }
@@ -133,6 +132,22 @@ end
133
132
  publisher.publish({ my: "data" })
134
133
  ```
135
134
 
135
+ ### Publish delayed messages
136
+
137
+ ```ruby
138
+ publisher = Twingly::AMQP::DefaultExchangePublisher.delayed(
139
+ delay_queue_name: "my_queue.delayed", # Queue where delayed messages will
140
+ # wait until delay_ms has passed
141
+ target_queue_name: "my_queue", # Queue which delayed messages will be
142
+ # published to after the delay has elapsed
143
+ delay_ms: 60_000,
144
+ )
145
+
146
+ # Publishes message to the delay queue. After delay_ms has passed,
147
+ # the message will be rerouted to the target queue specified above
148
+ publisher.publish({ my: "data" })
149
+ ```
150
+
136
151
  ### Ping urls
137
152
 
138
153
  ```ruby
@@ -145,9 +160,11 @@ pinger = Twingly::AMQP::Pinger.new(
145
160
 
146
161
  # Optional, options can also be given to #ping
147
162
  pinger.default_ping_options do |options|
148
- options.provider_name = "TestProvider"
149
- options.source_ip = "?.?.?.?"
150
- options.priority = 1
163
+ options.provider_name = "TestProvider"
164
+ options.source_ip = "?.?.?.?"
165
+ options.priority = 1
166
+ # Optional keys/values that will be included in each ping message
167
+ options.custom_options = { my_option: "This is my own option" }
151
168
  end
152
169
 
153
170
  urls = [
@@ -155,10 +172,12 @@ urls = [
155
172
  ]
156
173
 
157
174
  # Optional, is merged with the default options above
175
+ # options given to #ping takes precedence over the default options
158
176
  options = {
159
177
  provider_name: "a-provider-name",
160
178
  source_ip: "?.?.?.?",
161
179
  priority: 1,
180
+ custom_options: { my_other_option: "Another option" },
162
181
  }
163
182
 
164
183
  pinger.ping(urls, options) do |pinged_url|
@@ -195,6 +214,12 @@ connection = Twingly::AMQP::Connection.instance
195
214
 
196
215
  The integration tests run by default and require a local RabbitMQ server to run.
197
216
 
217
+ Start RabbitMQ server with
218
+
219
+ ```shell
220
+ docker-compose up
221
+ ```
222
+
198
223
  Run tests with
199
224
 
200
225
  ```shell
@@ -211,10 +236,12 @@ bundle exec rake spec:unit
211
236
 
212
237
  To run static code analysis:
213
238
 
214
- bundle exec rubocop
239
+ gem install rubocop
240
+
241
+ rubocop
215
242
 
216
- # optionally on single file(s)
217
- bundle exec rubocop lib/twingly/amqp/*
243
+ # optionally on single file(s)
244
+ rubocop lib/twingly/amqp/*
218
245
 
219
246
  ## Release workflow
220
247
 
@@ -224,7 +251,7 @@ To run static code analysis:
224
251
 
225
252
  bundle exec rake release
226
253
 
227
- * If you are not logged in as [twingly][twingly-rubygems] with ruby gems, the rake task will fail and tell you to set credentials via `gem push`, do that and run the `release` task again. It will be okay.
254
+ * If you are not logged in as [twingly][twingly-rubygems] with ruby gems, the rake task will fail. Login using `gem signin` and run the `release` task again. It will be okay.
228
255
 
229
256
  * Update the changelog with [GitHub Changelog Generator](https://github.com/skywinder/github-changelog-generator/) (`gem install github_changelog_generator` if you don't have it, set `CHANGELOG_GITHUB_TOKEN` to a personal access token to avoid rate limiting by GitHub). This command will update `CHANGELOG.md`, commit and push manually.
230
257
 
data/lib/twingly/amqp.rb CHANGED
@@ -1,13 +1,18 @@
1
1
  require "twingly/amqp/version"
2
2
  require "twingly/amqp/session"
3
3
  require "twingly/amqp/connection"
4
+ require "twingly/amqp/utilities"
4
5
  require "twingly/amqp/subscription"
5
6
  require "twingly/amqp/ping_options"
6
7
  require "twingly/amqp/pinger"
8
+ require "twingly/amqp/publisher"
7
9
  require "twingly/amqp/default_exchange_publisher"
8
10
  require "twingly/amqp/topic_exchange_publisher"
9
11
  require "twingly/amqp/null_logger"
12
+ require "twingly/amqp/message"
10
13
 
14
+ require "bunny"
15
+ require "json"
11
16
  require "ostruct"
12
17
 
13
18
  module Twingly
@@ -1,5 +1,3 @@
1
- require "twingly/amqp"
2
-
3
1
  module Twingly
4
2
  module AMQP
5
3
  class Connection
@@ -1,47 +1,28 @@
1
- require "twingly/amqp/connection"
2
- require "json"
3
- require "ostruct"
4
-
5
1
  module Twingly
6
2
  module AMQP
7
3
  class DefaultExchangePublisher
8
- def initialize(queue_name:, connection: nil)
9
- options.routing_key = queue_name
4
+ include Publisher
10
5
 
11
- connection ||= Connection.instance
12
- @exchange = connection.create_channel.default_exchange
13
- end
14
-
15
- def publish(message)
16
- raise ArgumentError unless message.respond_to?(:to_h)
17
-
18
- payload = message.to_h.to_json
19
- opts = options.to_h
20
- @exchange.publish(payload, opts)
21
- end
6
+ DEFAULT_EXCHANGE = ""
22
7
 
23
- # only used by tests to avoid sleeping
24
- def publish_with_confirm(message)
25
- channel = @exchange.channel
26
- channel.confirm_select unless channel.using_publisher_confirmations?
27
-
28
- publish(message)
29
-
30
- @exchange.wait_for_confirms
31
- end
8
+ def initialize(queue_name:, connection: Connection.instance)
9
+ options.routing_key = queue_name
32
10
 
33
- def configure_publish_options
34
- yield options
11
+ @exchange = connection.create_channel.default_exchange
35
12
  end
36
13
 
37
- private
38
-
39
- def options
40
- @options ||=
41
- OpenStruct.new(
42
- content_type: "application/json",
43
- persistent: true,
44
- )
14
+ def self.delayed(delay_queue_name:, target_queue_name:, delay_ms:, connection: Connection.instance)
15
+ Utilities.create_queue(delay_queue_name,
16
+ arguments: {
17
+ "x-dead-letter-exchange": DEFAULT_EXCHANGE,
18
+ "x-dead-letter-routing-key": target_queue_name,
19
+ })
20
+
21
+ new(queue_name: delay_queue_name, connection: connection).tap do |publisher|
22
+ publisher.configure_publish_options do |options|
23
+ options.expiration = delay_ms
24
+ end
25
+ end
45
26
  end
46
27
  end
47
28
  end
@@ -1,5 +1,3 @@
1
- require "json"
2
-
3
1
  module Twingly
4
2
  module AMQP
5
3
  class Message
@@ -3,25 +3,40 @@ module Twingly
3
3
  class PingOptions
4
4
  attr_accessor :provider_name, :source_ip, :priority
5
5
 
6
- def initialize(provider_name: nil, source_ip: nil, priority: nil)
7
- self.provider_name = provider_name
8
- self.source_ip = source_ip
9
- self.priority = priority
6
+ attr_reader :custom_options
7
+
8
+ def initialize(provider_name: nil, source_ip: nil, priority: nil,
9
+ custom_options: {})
10
+ self.provider_name = provider_name
11
+ self.source_ip = source_ip
12
+ self.priority = priority
13
+ self.custom_options = custom_options
10
14
 
11
15
  yield self if block_given?
12
16
  end
13
17
 
18
+ def custom_options=(options)
19
+ unless options.respond_to?(:to_h)
20
+ raise ArgumentError, "custom_options must respond to 'to_h'"
21
+ end
22
+
23
+ @custom_options = options.to_h
24
+ end
25
+
14
26
  def to_h
15
27
  {
16
- automatic_ping: false,
17
28
  provider_name: provider_name,
18
29
  source_ip: source_ip,
19
30
  priority: priority,
31
+ custom_options: custom_options,
20
32
  }
21
33
  end
22
34
 
23
35
  def validate
24
- missing_keys = to_h.select { |_, value| value.to_s.empty? }.keys
36
+ missing_keys = to_h.select do |key, value|
37
+ key != :custom_options && value.to_s.empty?
38
+ end.keys
39
+
25
40
  if missing_keys.any?
26
41
  raise ArgumentError, "Required options not set: #{missing_keys}"
27
42
  end
@@ -29,9 +44,10 @@ module Twingly
29
44
 
30
45
  def merge(other)
31
46
  PingOptions.new do |options|
32
- options.provider_name = other.provider_name || provider_name
33
- options.source_ip = other.source_ip || source_ip
34
- options.priority = other.priority || priority
47
+ options.provider_name = other.provider_name || provider_name
48
+ options.source_ip = other.source_ip || source_ip
49
+ options.priority = other.priority || priority
50
+ options.custom_options = custom_options.merge(other.custom_options)
35
51
  end
36
52
  end
37
53
  end
@@ -1,14 +1,8 @@
1
- require "twingly/amqp/connection"
2
- require "twingly/amqp/ping_options"
3
- require "twingly/amqp/default_exchange_publisher"
4
- require "json"
5
-
6
1
  module Twingly
7
2
  module AMQP
8
3
  class Pinger
9
- def initialize(queue_name:, ping_expiration: nil, url_cache: NullCache, connection: nil, confirm_publish: false)
4
+ def initialize(queue_name:, ping_expiration: nil, url_cache: NullCache, connection: Connection.instance, confirm_publish: false)
10
5
  @url_cache = url_cache
11
- connection ||= Connection.instance
12
6
 
13
7
  @publisher = DefaultExchangePublisher.new(queue_name: queue_name, connection: connection)
14
8
  @publisher.configure_publish_options do |options|
@@ -20,18 +14,17 @@ module Twingly
20
14
  end
21
15
 
22
16
  def ping(urls, options_hash = {})
23
- options = PingOptions.new(options_hash)
17
+ options = PingOptions.new(**options_hash)
24
18
  options = @default_ping_options.merge(options)
25
19
 
26
20
  options.validate
27
21
 
28
22
  Array(urls).each do |url|
29
- unless cached?(url)
30
- publish(url, options)
31
- cache!(url)
23
+ next if cached?(url)
24
+ publish(url, options)
25
+ cache!(url)
32
26
 
33
- yield url if block_given?
34
- end
27
+ yield url if block_given?
35
28
  end
36
29
  end
37
30
 
@@ -67,7 +60,7 @@ module Twingly
67
60
  end
68
61
 
69
62
  class NullCache
70
- def self.cached?(url)
63
+ def self.cached?(_url)
71
64
  false
72
65
  end
73
66
 
@@ -0,0 +1,44 @@
1
+ module Twingly
2
+ module AMQP
3
+ module Publisher
4
+ def publish(message)
5
+ payload =
6
+ if message.kind_of?(Array)
7
+ message
8
+ elsif message.respond_to?(:to_h)
9
+ message.to_h
10
+ else
11
+ raise ArgumentError
12
+ end
13
+
14
+ json_payload = payload.to_json
15
+ opts = options.to_h
16
+ @exchange.publish(json_payload, opts)
17
+ end
18
+
19
+ # only used by tests to avoid sleeping
20
+ def publish_with_confirm(message)
21
+ channel = @exchange.channel
22
+ channel.confirm_select unless channel.using_publisher_confirmations?
23
+
24
+ publish(message)
25
+
26
+ @exchange.wait_for_confirms
27
+ end
28
+
29
+ def configure_publish_options
30
+ yield options
31
+ end
32
+
33
+ private
34
+
35
+ def options
36
+ @options ||=
37
+ OpenStruct.new(
38
+ content_type: "application/json",
39
+ persistent: true,
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,3 @@
1
- require "bunny"
2
-
3
1
  module Twingly
4
2
  module AMQP
5
3
  class Session
@@ -27,10 +25,6 @@ module Twingly
27
25
  options[:pass] ||= password_from_env
28
26
  options[:hosts] ||= hosts_from_env
29
27
 
30
- # Used as a workaround for
31
- # https://github.com/ruby-amqp/bunny/issues/446
32
- options[:hosts] = options.fetch(:hosts).dup
33
-
34
28
  default_connection_options.merge(options)
35
29
  end
36
30
 
@@ -1,26 +1,31 @@
1
- require "twingly/amqp/connection"
2
- require "twingly/amqp/message"
3
-
4
1
  module Twingly
5
2
  module AMQP
6
3
  class Subscription
7
4
  def initialize(queue_name:, exchange_topic: nil, routing_key: nil,
8
- consumer_threads: 4, prefetch: 20, connection: nil,
9
- max_length: nil)
5
+ routing_keys: nil, consumer_threads: 1, prefetch: 20,
6
+ connection: Connection.instance, max_length: nil)
10
7
  @queue_name = queue_name
11
8
  @exchange_topic = exchange_topic
12
- @routing_key = routing_key
9
+ @routing_keys = Array(routing_keys || routing_key)
13
10
  @consumer_threads = consumer_threads
14
11
  @prefetch = prefetch
15
12
  @max_length = max_length
13
+ @cancel = false
14
+
15
+ if routing_key
16
+ warn "[DEPRECATION] `routing_key` is deprecated. "\
17
+ "Please use `routing_keys` instead."
18
+ end
16
19
 
17
- connection ||= Connection.instance
18
20
  @channel = create_channel(connection)
19
21
  @queue = @channel.queue(@queue_name, queue_options)
20
22
 
21
- if @exchange_topic && @routing_key
23
+ if @exchange_topic && @routing_keys.any?
22
24
  exchange = @channel.topic(@exchange_topic, durable: true)
23
- @queue.bind(exchange, routing_key: @routing_key)
25
+
26
+ @routing_keys.each do |routing_key|
27
+ @queue.bind(exchange, routing_key: routing_key)
28
+ end
24
29
  end
25
30
 
26
31
  @before_handle_message_callback = proc {}
@@ -63,7 +68,7 @@ module Twingly
63
68
 
64
69
  private
65
70
 
66
- def create_consumer(&block)
71
+ def create_consumer
67
72
  @queue.subscribe(subscribe_options) do |delivery_info, metadata, payload|
68
73
  @before_handle_message_callback.call(payload)
69
74
 
@@ -74,7 +79,7 @@ module Twingly
74
79
  channel: @channel,
75
80
  )
76
81
 
77
- block.call(message)
82
+ yield message
78
83
  end
79
84
  end
80
85
 
@@ -112,18 +117,6 @@ module Twingly
112
117
  tag_name = Socket.gethostname
113
118
  @channel.generate_consumer_tag(tag_name)
114
119
  end
115
-
116
- def setup_traps
117
- [:INT, :TERM].each do |signal|
118
- Signal.trap(signal) do
119
- # Exit fast if we've already got a signal since before
120
- exit!(true) if cancel?
121
-
122
- # Set cancel flag, cancels consumers
123
- cancel!
124
- end
125
- end
126
- end
127
120
  end
128
121
  end
129
122
  end
@@ -1,48 +1,13 @@
1
- require "twingly/amqp/connection"
2
- require "json"
3
- require "ostruct"
4
-
5
1
  module Twingly
6
2
  module AMQP
7
3
  class TopicExchangePublisher
8
- def initialize(exchange_name:, routing_key: nil, connection: nil, opts: {})
4
+ include Publisher
5
+
6
+ def initialize(exchange_name:, routing_key: nil, connection: Connection.instance, opts: {})
9
7
  options.routing_key = routing_key
10
8
 
11
- connection ||= Connection.instance
12
9
  @exchange = connection.create_channel.topic(exchange_name, opts)
13
10
  end
14
-
15
- def publish(message)
16
- raise ArgumentError unless message.respond_to?(:to_h)
17
-
18
- payload = message.to_h.to_json
19
- opts = options.to_h
20
- @exchange.publish(payload, opts)
21
- end
22
-
23
- # only used by tests to avoid sleeping
24
- def publish_with_confirm(message)
25
- channel = @exchange.channel
26
- channel.confirm_select unless channel.using_publisher_confirmations?
27
-
28
- publish(message)
29
-
30
- @exchange.wait_for_confirms
31
- end
32
-
33
- def configure_publish_options
34
- yield options
35
- end
36
-
37
- private
38
-
39
- def options
40
- @options ||=
41
- OpenStruct.new(
42
- content_type: "application/json",
43
- persistent: true,
44
- )
45
- end
46
11
  end
47
12
  end
48
13
  end
@@ -0,0 +1,15 @@
1
+ module Twingly
2
+ module AMQP
3
+ module Utilities
4
+ def self.create_queue(queue_name, durable: true, arguments: {}, connection: Connection.instance)
5
+ connection.with_channel do |channel|
6
+ return channel.queue(
7
+ queue_name,
8
+ durable: durable,
9
+ arguments: arguments,
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  module Twingly
2
2
  module Amqp
3
- VERSION = "4.3.0".freeze
3
+ VERSION = "5.2.0".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,71 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twingly-amqp
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.0
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Twingly AB
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-19 00:00:00.000000000 Z
11
+ date: 2021-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.2'
19
+ version: 2.7.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: '2.2'
27
- - !ruby/object:Gem::Dependency
28
- name: rspec
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '3'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '3'
26
+ version: 2.7.3
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rake
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: '10.0'
33
+ version: '12'
48
34
  type: :development
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: '10.0'
40
+ version: '12'
55
41
  - !ruby/object:Gem::Dependency
56
- name: rubocop
42
+ name: rspec
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - "~>"
60
46
  - !ruby/object:Gem::Version
61
- version: '0'
47
+ version: '3'
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
- version: '0'
54
+ version: '3'
69
55
  description: Publish and subscribe to messages via RabbitMQ
70
56
  email:
71
57
  - support@twingly.com
@@ -81,14 +67,16 @@ files:
81
67
  - lib/twingly/amqp/null_logger.rb
82
68
  - lib/twingly/amqp/ping_options.rb
83
69
  - lib/twingly/amqp/pinger.rb
70
+ - lib/twingly/amqp/publisher.rb
84
71
  - lib/twingly/amqp/session.rb
85
72
  - lib/twingly/amqp/subscription.rb
86
73
  - lib/twingly/amqp/topic_exchange_publisher.rb
74
+ - lib/twingly/amqp/utilities.rb
87
75
  - lib/twingly/amqp/version.rb
88
76
  homepage: https://github.com/twingly/twingly-amqp
89
77
  licenses: []
90
78
  metadata: {}
91
- post_install_message:
79
+ post_install_message:
92
80
  rdoc_options: []
93
81
  require_paths:
94
82
  - lib
@@ -103,9 +91,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
91
  - !ruby/object:Gem::Version
104
92
  version: '0'
105
93
  requirements: []
106
- rubyforge_project:
107
- rubygems_version: 2.5.2
108
- signing_key:
94
+ rubygems_version: 3.1.2
95
+ signing_key:
109
96
  specification_version: 4
110
97
  summary: Ruby library for talking to RabbitMQ
111
98
  test_files: []