aws-sdk-resources 2.11.549
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 +7 -0
- data/lib/aws-sdk-resources.rb +91 -0
- data/lib/aws-sdk-resources/batch.rb +143 -0
- data/lib/aws-sdk-resources/builder.rb +85 -0
- data/lib/aws-sdk-resources/builder_sources.rb +105 -0
- data/lib/aws-sdk-resources/collection.rb +107 -0
- data/lib/aws-sdk-resources/definition.rb +331 -0
- data/lib/aws-sdk-resources/documenter.rb +70 -0
- data/lib/aws-sdk-resources/documenter/base_operation_documenter.rb +279 -0
- data/lib/aws-sdk-resources/documenter/data_operation_documenter.rb +25 -0
- data/lib/aws-sdk-resources/documenter/has_many_operation_documenter.rb +69 -0
- data/lib/aws-sdk-resources/documenter/has_operation_documenter.rb +66 -0
- data/lib/aws-sdk-resources/documenter/operation_documenter.rb +20 -0
- data/lib/aws-sdk-resources/documenter/resource_operation_documenter.rb +53 -0
- data/lib/aws-sdk-resources/documenter/waiter_operation_documenter.rb +77 -0
- data/lib/aws-sdk-resources/errors.rb +15 -0
- data/lib/aws-sdk-resources/operation_methods.rb +83 -0
- data/lib/aws-sdk-resources/operations.rb +280 -0
- data/lib/aws-sdk-resources/options.rb +17 -0
- data/lib/aws-sdk-resources/request.rb +39 -0
- data/lib/aws-sdk-resources/request_params.rb +140 -0
- data/lib/aws-sdk-resources/resource.rb +243 -0
- data/lib/aws-sdk-resources/services/ec2.rb +21 -0
- data/lib/aws-sdk-resources/services/ec2/instance.rb +29 -0
- data/lib/aws-sdk-resources/services/iam.rb +19 -0
- data/lib/aws-sdk-resources/services/s3.rb +20 -0
- data/lib/aws-sdk-resources/services/s3/bucket.rb +131 -0
- data/lib/aws-sdk-resources/services/s3/encryption.rb +21 -0
- data/lib/aws-sdk-resources/services/s3/encryption/client.rb +369 -0
- data/lib/aws-sdk-resources/services/s3/encryption/decrypt_handler.rb +174 -0
- data/lib/aws-sdk-resources/services/s3/encryption/default_cipher_provider.rb +63 -0
- data/lib/aws-sdk-resources/services/s3/encryption/default_key_provider.rb +38 -0
- data/lib/aws-sdk-resources/services/s3/encryption/encrypt_handler.rb +50 -0
- data/lib/aws-sdk-resources/services/s3/encryption/errors.rb +13 -0
- data/lib/aws-sdk-resources/services/s3/encryption/io_auth_decrypter.rb +56 -0
- data/lib/aws-sdk-resources/services/s3/encryption/io_decrypter.rb +29 -0
- data/lib/aws-sdk-resources/services/s3/encryption/io_encrypter.rb +69 -0
- data/lib/aws-sdk-resources/services/s3/encryption/key_provider.rb +29 -0
- data/lib/aws-sdk-resources/services/s3/encryption/kms_cipher_provider.rb +71 -0
- data/lib/aws-sdk-resources/services/s3/encryption/materials.rb +58 -0
- data/lib/aws-sdk-resources/services/s3/encryption/utils.rb +79 -0
- data/lib/aws-sdk-resources/services/s3/file_downloader.rb +169 -0
- data/lib/aws-sdk-resources/services/s3/file_part.rb +75 -0
- data/lib/aws-sdk-resources/services/s3/file_uploader.rb +58 -0
- data/lib/aws-sdk-resources/services/s3/multipart_file_uploader.rb +187 -0
- data/lib/aws-sdk-resources/services/s3/multipart_upload.rb +42 -0
- data/lib/aws-sdk-resources/services/s3/multipart_upload_error.rb +16 -0
- data/lib/aws-sdk-resources/services/s3/object.rb +290 -0
- data/lib/aws-sdk-resources/services/s3/object_copier.rb +99 -0
- data/lib/aws-sdk-resources/services/s3/object_multipart_copier.rb +180 -0
- data/lib/aws-sdk-resources/services/s3/object_summary.rb +73 -0
- data/lib/aws-sdk-resources/services/s3/presigned_post.rb +651 -0
- data/lib/aws-sdk-resources/services/sns.rb +7 -0
- data/lib/aws-sdk-resources/services/sns/message_verifier.rb +171 -0
- data/lib/aws-sdk-resources/services/sqs.rb +7 -0
- data/lib/aws-sdk-resources/services/sqs/queue_poller.rb +521 -0
- data/lib/aws-sdk-resources/source.rb +39 -0
- metadata +118 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'openssl'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module Aws
|
6
|
+
module SNS
|
7
|
+
|
8
|
+
# A utility class that can be used to verify the authenticity of messages
|
9
|
+
# sent by Amazon SNS.
|
10
|
+
#
|
11
|
+
# verifier = Aws::SNS::MessageVerifier.new
|
12
|
+
#
|
13
|
+
# # returns true/false
|
14
|
+
# verifier.authentic?(message_body)
|
15
|
+
#
|
16
|
+
# # raises a Aws::SNS::MessageVerifier::VerificationError on failure
|
17
|
+
# verifier.authenticate!(message_body)
|
18
|
+
#
|
19
|
+
# You can re-use a single {MessageVerifier} instance to authenticate
|
20
|
+
# multiple SNS messages.
|
21
|
+
class MessageVerifier
|
22
|
+
|
23
|
+
class VerificationError < StandardError; end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
SIGNABLE_KEYS = [
|
27
|
+
'Message',
|
28
|
+
'MessageId',
|
29
|
+
'Subject',
|
30
|
+
'SubscribeURL',
|
31
|
+
'Timestamp',
|
32
|
+
'Token',
|
33
|
+
'TopicArn',
|
34
|
+
'Type',
|
35
|
+
].freeze
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
AWS_HOSTNAMES = [
|
39
|
+
/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/
|
40
|
+
]
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@cached_pems = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [String<JSON>] message_body
|
47
|
+
# @return [Boolean] Returns `true` if the given message has been
|
48
|
+
# successfully verified. Returns `false` otherwise.
|
49
|
+
def authentic?(message_body)
|
50
|
+
authenticate!(message_body)
|
51
|
+
rescue VerificationError
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param [String<JSON>] message_body
|
56
|
+
# @return [Boolean] Returns `true` when the given message has been
|
57
|
+
# successfully verified.
|
58
|
+
# @raise [VerificationError] Raised when the given message has failed
|
59
|
+
# verification.
|
60
|
+
def authenticate!(message_body)
|
61
|
+
msg = Json.load(message_body)
|
62
|
+
msg = convert_lambda_msg(msg) if is_from_lambda(msg)
|
63
|
+
if public_key(msg).verify(sha1, signature(msg), canonical_string(msg))
|
64
|
+
true
|
65
|
+
else
|
66
|
+
msg = 'the authenticity of the message cannot be verified'
|
67
|
+
raise VerificationError, msg
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def is_from_lambda(message)
|
74
|
+
message.key? 'SigningCertUrl'
|
75
|
+
end
|
76
|
+
|
77
|
+
def convert_lambda_msg(message)
|
78
|
+
cert_url = message.delete('SigningCertUrl')
|
79
|
+
unsubscribe_url = message.delete('UnsubscribeUrl')
|
80
|
+
|
81
|
+
message['SigningCertURL'] = cert_url
|
82
|
+
message['UnsubscribeURL'] = unsubscribe_url
|
83
|
+
message
|
84
|
+
end
|
85
|
+
|
86
|
+
def sha1
|
87
|
+
OpenSSL::Digest::SHA1.new
|
88
|
+
end
|
89
|
+
|
90
|
+
def signature(message)
|
91
|
+
Base64.decode64(message['Signature'])
|
92
|
+
end
|
93
|
+
|
94
|
+
def canonical_string(message)
|
95
|
+
parts = []
|
96
|
+
SIGNABLE_KEYS.each do |key|
|
97
|
+
value = message[key]
|
98
|
+
unless value.nil? or value.empty?
|
99
|
+
parts << "#{key}\n#{value}\n"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
parts.join
|
103
|
+
end
|
104
|
+
|
105
|
+
def public_key(message)
|
106
|
+
x509_url = URI.parse(message['SigningCertURL'])
|
107
|
+
x509 = OpenSSL::X509::Certificate.new(pem(x509_url))
|
108
|
+
OpenSSL::PKey::RSA.new(x509.public_key)
|
109
|
+
end
|
110
|
+
|
111
|
+
def pem(uri)
|
112
|
+
if @cached_pems[uri.to_s]
|
113
|
+
@cached_pems[uri.to_s]
|
114
|
+
else
|
115
|
+
@cached_pems[uri.to_s] = download_pem(uri)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def download_pem(uri)
|
120
|
+
verify_uri!(uri)
|
121
|
+
https_get(uri)
|
122
|
+
end
|
123
|
+
|
124
|
+
def verify_uri!(uri)
|
125
|
+
verify_https!(uri)
|
126
|
+
verify_hosted_by_aws!(uri)
|
127
|
+
verify_pem!(uri)
|
128
|
+
end
|
129
|
+
|
130
|
+
def verify_https!(uri)
|
131
|
+
unless uri.scheme == 'https'
|
132
|
+
msg = "the SigningCertURL must be https, got: #{uri}"
|
133
|
+
raise VerificationError, msg
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def verify_hosted_by_aws!(uri)
|
138
|
+
unless AWS_HOSTNAMES.any? { |pattern| pattern.match(uri.host) }
|
139
|
+
msg = "signing cert is not hosted by AWS: #{uri}"
|
140
|
+
raise VerificationError, msg
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def verify_pem!(uri)
|
145
|
+
unless File.extname(uri.path) == '.pem'
|
146
|
+
msg = "the SigningCertURL must link to a .pem file"
|
147
|
+
raise VerificationError, msg
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def https_get(uri, failed_attempts = 0)
|
152
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
153
|
+
http.use_ssl = true
|
154
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
155
|
+
http.start
|
156
|
+
resp = http.request(Net::HTTP::Get.new(uri.request_uri))
|
157
|
+
http.finish
|
158
|
+
if resp.code == '200'
|
159
|
+
resp.body
|
160
|
+
else
|
161
|
+
raise VerificationError, resp.body
|
162
|
+
end
|
163
|
+
rescue => error
|
164
|
+
failed_attempts += 1
|
165
|
+
retry if failed_attempts < 3
|
166
|
+
raise VerificationError, error.message
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,521 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module SQS
|
5
|
+
|
6
|
+
# A utility class for long polling messages in a loop. **Messages are
|
7
|
+
# automatically deleted from the queue at the end of the given block.**
|
8
|
+
#
|
9
|
+
# poller = Aws::SQS::QueuePoller.new(queue_url)
|
10
|
+
#
|
11
|
+
# poller.poll do |msg|
|
12
|
+
# puts msg.body
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# ## Long Polling
|
16
|
+
#
|
17
|
+
# By default, messages are received using long polling. This
|
18
|
+
# method will force a default `:wait_time_seconds` of 20 seconds.
|
19
|
+
# If you prefer to use the queue default wait time, then pass
|
20
|
+
# a `nil` value for `:wait_time_seconds`.
|
21
|
+
#
|
22
|
+
# # disables 20 second default, use queue ReceiveMessageWaitTimeSeconds
|
23
|
+
# poller.poll(wait_time_seconds:nil) do |msg|
|
24
|
+
# # ...
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# When disabling `:wait_time_seconds` by passing `nil`, you must
|
28
|
+
# ensure the queue `ReceiveMessageWaitTimeSeconds` attribute is
|
29
|
+
# set to a non-zero value, or you will be short-polling.
|
30
|
+
# This will trigger significantly more API calls.
|
31
|
+
#
|
32
|
+
# ## Batch Receiving Messages
|
33
|
+
#
|
34
|
+
# You can specify a maximum number of messages to receive with
|
35
|
+
# each polling attempt via `:max_number_of_messages`. When this is
|
36
|
+
# set to a positive value, greater than 1, the block will receive
|
37
|
+
# an array of messages, instead of a single message.
|
38
|
+
#
|
39
|
+
# # receives and yields 1 message at a time
|
40
|
+
# poller.poll do |msg|
|
41
|
+
# # ...
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# # receives and yields up to 10 messages at a time
|
45
|
+
# poller.poll(max_number_of_messages:10) do |messages|
|
46
|
+
# messages.each do |msg|
|
47
|
+
# # ...
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# The maximum value for `:max_number_of_messages` is enforced by
|
52
|
+
# Amazon SQS.
|
53
|
+
#
|
54
|
+
# ## Visibility Timeouts
|
55
|
+
#
|
56
|
+
# When receiving messages, you have a fixed amount of time to process
|
57
|
+
# and delete the message before it is added back into the queue. This
|
58
|
+
# is the visibility timeout. By default, the queue's `VisibilityTimeout`
|
59
|
+
# attribute is used. You can provide an alternative visibility timeout
|
60
|
+
# when polling.
|
61
|
+
#
|
62
|
+
# # queue default VisibilityTimeout
|
63
|
+
# poller.poll do |msg|
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# # custom visibility timeout
|
67
|
+
# poller.poll(visibility_timeout:10) do |msg|
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
#
|
71
|
+
# You can reset the visibility timeout of a single message by calling
|
72
|
+
# {#change_message_visibility_timeout}. This is useful when you need
|
73
|
+
# more time to finish processing the message.
|
74
|
+
#
|
75
|
+
# poller.poll do |msg|
|
76
|
+
#
|
77
|
+
# # do work ...
|
78
|
+
#
|
79
|
+
# # need more time for processing
|
80
|
+
# poller.change_message_visibility_timeout(msg, 60)
|
81
|
+
#
|
82
|
+
# # finish work ...
|
83
|
+
#
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# If you change the visibility timeout of a message to zero, it will
|
87
|
+
# return to the queue immediately.
|
88
|
+
#
|
89
|
+
# ## Deleting Messages
|
90
|
+
#
|
91
|
+
# Messages are deleted from the queue when the block returns normally.
|
92
|
+
#
|
93
|
+
# poller.poll do |msg|
|
94
|
+
# # do work
|
95
|
+
# end # messages deleted here
|
96
|
+
#
|
97
|
+
# You can skip message deletion by passing `skip_delete: true`.
|
98
|
+
# This allows you to manually delete the messages using
|
99
|
+
# {#delete_message}, or {#delete_messages}.
|
100
|
+
#
|
101
|
+
# # single message
|
102
|
+
# poller.poll(skip_delete: true) do |msg|
|
103
|
+
# poller.delete_message(msg) # if successful
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# # batch delete messages
|
107
|
+
# poller.poll(skip_delete: true, max_number_of_messages:10) do |messages|
|
108
|
+
# poller.delete_messages(messages)
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# Another way to manage message deletion is to throw `:skip_delete`
|
112
|
+
# from the poll block. You can use this to choose when a message, or
|
113
|
+
# message batch is deleted on an individual basis. This can be very
|
114
|
+
# useful when you are capturing temporal errors and wish for the
|
115
|
+
# message to timeout.
|
116
|
+
#
|
117
|
+
# poller.poll do |msg|
|
118
|
+
# begin
|
119
|
+
# # do work
|
120
|
+
# rescue
|
121
|
+
# # unexpected error occurred while processing messages,
|
122
|
+
# # log it, and skip delete so it can be re-processed later
|
123
|
+
# throw :skip_delete
|
124
|
+
# end
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# ## Terminating the Polling Loop
|
128
|
+
#
|
129
|
+
# By default, polling will continue indefinitely. You can stop
|
130
|
+
# the poller by providing an idle timeout or by throwing `:stop_polling`
|
131
|
+
# from the {#before_request} callback.
|
132
|
+
#
|
133
|
+
# ### `:idle_timeout` Option
|
134
|
+
#
|
135
|
+
# This is a configurable, maximum number of seconds to wait for a
|
136
|
+
# new message before the polling loop exists. By default, there is
|
137
|
+
# no idle timeout.
|
138
|
+
#
|
139
|
+
# # stops polling after a minute of no received messages
|
140
|
+
# poller.poll(idle_timeout: 60) do |msg|
|
141
|
+
# # ...
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# ### Throw `:stop_polling`
|
145
|
+
#
|
146
|
+
# If you want more fine grained control, you can configure a
|
147
|
+
# before request callback to trigger before each long poll. Throwing
|
148
|
+
# `:stop_polling` from this callback will cause the poller to exit
|
149
|
+
# normally without making the next request.
|
150
|
+
#
|
151
|
+
# # stop after processing 100 messages
|
152
|
+
# poller.before_request do |stats|
|
153
|
+
# throw :stop_polling if stats.received_message_count >= 100
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# poller.poll do |msg|
|
157
|
+
# # do work ...
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# ## Tracking Progress
|
161
|
+
#
|
162
|
+
# The poller will automatically track a few statistics client-side in
|
163
|
+
# a {PollerStats} object. You can access the poller stats
|
164
|
+
# three ways:
|
165
|
+
#
|
166
|
+
# * The first block argument of {#before_request}
|
167
|
+
# * The second block argument of {#poll}.
|
168
|
+
# * The return value from {#poll}.
|
169
|
+
#
|
170
|
+
# Here are examples of accessing the statistics.
|
171
|
+
#
|
172
|
+
# * Configure a {#before_request} callback.
|
173
|
+
#
|
174
|
+
# ```
|
175
|
+
# poller.before_request do |stats|
|
176
|
+
# logger.info("requests: #{stats.request_count}")
|
177
|
+
# logger.info("messages: #{stats.received_message_count}")
|
178
|
+
# logger.info("last-timestamp: #{stats.last_message_received_at}")
|
179
|
+
# end
|
180
|
+
# ```
|
181
|
+
#
|
182
|
+
# * Accept a 2nd argument in the poll block, for example:
|
183
|
+
#
|
184
|
+
# ```
|
185
|
+
# poller.poll do |msg, stats|
|
186
|
+
# logger.info("requests: #{stats.request_count}")
|
187
|
+
# logger.info("messages: #{stats.received_message_count}")
|
188
|
+
# logger.info("last-timestamp: #{stats.last_message_received_at}")
|
189
|
+
# end
|
190
|
+
# ```
|
191
|
+
#
|
192
|
+
# * Return value:
|
193
|
+
#
|
194
|
+
# ```
|
195
|
+
# stats = poller.poll(idle_timeout:10) do |msg|
|
196
|
+
# # do work ...
|
197
|
+
# end
|
198
|
+
# logger.info("requests: #{stats.request_count}")
|
199
|
+
# logger.info("messages: #{stats.received_message_count}")
|
200
|
+
# logger.info("last-timestamp: #{stats.last_message_received_at}")
|
201
|
+
# ```
|
202
|
+
#
|
203
|
+
class QueuePoller
|
204
|
+
|
205
|
+
# @param [String] queue_url
|
206
|
+
# @option options [Client] :client
|
207
|
+
# @option (see #poll)
|
208
|
+
def initialize(queue_url, options = {})
|
209
|
+
@queue_url = queue_url
|
210
|
+
@client = options.delete(:client) || Client.new
|
211
|
+
@default_config = PollerConfig.new(options)
|
212
|
+
end
|
213
|
+
|
214
|
+
# @return [String]
|
215
|
+
attr_reader :queue_url
|
216
|
+
|
217
|
+
# @return [Client]
|
218
|
+
attr_reader :client
|
219
|
+
|
220
|
+
# @return [PollerConfig]
|
221
|
+
attr_reader :default_config
|
222
|
+
|
223
|
+
# Registers a callback that is invoked once before every polling
|
224
|
+
# attempt.
|
225
|
+
#
|
226
|
+
# poller.before_request do |stats|
|
227
|
+
# logger.info("requests: #{stats.request_count}")
|
228
|
+
# logger.info("messages: #{stats.received_message_count}")
|
229
|
+
# logger.info("last-timestamp: #{stats.last_message_received_at}")
|
230
|
+
# end
|
231
|
+
#
|
232
|
+
# poller.poll do |msg|
|
233
|
+
# # do work ...
|
234
|
+
# end
|
235
|
+
#
|
236
|
+
# ## `:stop_polling`
|
237
|
+
#
|
238
|
+
# If you throw `:stop_polling` from the {#before_request} callback,
|
239
|
+
# then the poller will exit normally before making the next long
|
240
|
+
# poll request.
|
241
|
+
#
|
242
|
+
# poller.before_request do |stats|
|
243
|
+
# throw :stop_polling if stats.received_messages >= 100
|
244
|
+
# end
|
245
|
+
#
|
246
|
+
# # at most 100 messages will be yielded
|
247
|
+
# poller.poll do |msg|
|
248
|
+
# # do work ...
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# @yieldparam [PollerStats] stats An object that tracks a few
|
252
|
+
# client-side statistics about the queue polling.
|
253
|
+
#
|
254
|
+
# @return [void]
|
255
|
+
def before_request(&block)
|
256
|
+
@default_config = @default_config.with(before_request: block) if block_given?
|
257
|
+
end
|
258
|
+
|
259
|
+
# Polls the queue, yielded a message, or an array of messages.
|
260
|
+
# Messages are automatically deleted from the queue at the
|
261
|
+
# end of the given block. See the class documentation on
|
262
|
+
# {QueuePoller} for more examples.
|
263
|
+
#
|
264
|
+
# @example Basic example, loops indefinitely
|
265
|
+
#
|
266
|
+
# poller.poll do |msg|
|
267
|
+
# # ...
|
268
|
+
# end
|
269
|
+
#
|
270
|
+
# @example Receives and deletes messages as a batch
|
271
|
+
#
|
272
|
+
# poller.poll(max_number_of_messages:10) do |messages|
|
273
|
+
# messages.each do |msg|
|
274
|
+
# # ...
|
275
|
+
# end
|
276
|
+
# end
|
277
|
+
#
|
278
|
+
# @option options [Integer] :wait_time_seconds (20) The
|
279
|
+
# long polling interval. Messages are yielded as soon as they are
|
280
|
+
# received. The `:wait_time_seconds` option specifies the max
|
281
|
+
# duration for each polling attempt before a new request is
|
282
|
+
# sent to receive messages.
|
283
|
+
#
|
284
|
+
# @option options [Integer] :max_number_of_messages (1) The maximum
|
285
|
+
# number of messages to yield from each polling attempt.
|
286
|
+
# Values can be from 1 to 10.
|
287
|
+
#
|
288
|
+
# @option options [Integer] :visibility_timeout (nil)
|
289
|
+
# The number of seconds you have to process a message before
|
290
|
+
# it is put back into the queue and can be received again.
|
291
|
+
# By default, the queue's
|
292
|
+
#
|
293
|
+
# @option options [Array<String>] :attribute_names ([])
|
294
|
+
# The list of attributes that need to be returned along with each
|
295
|
+
# message. Valid attribute names include:
|
296
|
+
#
|
297
|
+
# * `All` - All attributes.
|
298
|
+
# * `ApproximateFirstReceiveTimestamp` - The time when the message
|
299
|
+
# was first received from the queue (epoch time in milliseconds).
|
300
|
+
# * `ApproximateReceiveCount` - The number of times a message has
|
301
|
+
# been received from the queue but not deleted.
|
302
|
+
# * `SenderId` - The AWS account number (or the IP address, if
|
303
|
+
# anonymous access is allowed) of the sender.
|
304
|
+
# * `SentTimestamp` - The time when the message was sent to the
|
305
|
+
# queue (epoch time in milliseconds).
|
306
|
+
#
|
307
|
+
# @option options [Array<String>] :message_attribute_names ([])
|
308
|
+
# A list of message attributes to receive. You can receive
|
309
|
+
# all messages by using `All` or `.*`. You can also use
|
310
|
+
# `foo.*` to return all message attributes starting with the
|
311
|
+
# `foo` prefix.
|
312
|
+
#
|
313
|
+
# @option options [Integer] :idle_timeout (nil) Polling terminates
|
314
|
+
# gracefully when `:idle_timeout` seconds have passed without
|
315
|
+
# receiving any messages.
|
316
|
+
#
|
317
|
+
# @option options [Boolean] :skip_delete (false) When `true`, messages
|
318
|
+
# are not deleted after polling block. If you wish to delete
|
319
|
+
# received messages, you will need to call `#delete_message` or
|
320
|
+
# `#delete_messages` manually.
|
321
|
+
#
|
322
|
+
# @option options [Proc] :before_request (nil) Called before each
|
323
|
+
# polling attempt. This proc receives a single argument, an
|
324
|
+
# instance of {PollerStats}.
|
325
|
+
#
|
326
|
+
# @return [PollerStats]
|
327
|
+
def poll(options = {}, &block)
|
328
|
+
config = @default_config.with(options)
|
329
|
+
stats = PollerStats.new
|
330
|
+
catch(:stop_polling) do
|
331
|
+
loop do
|
332
|
+
messages = get_messages(config, stats)
|
333
|
+
if messages.empty?
|
334
|
+
check_idle_timeout(config, stats, messages)
|
335
|
+
else
|
336
|
+
process_messages(config, stats, messages, &block)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
stats.polling_stopped_at = Time.now
|
341
|
+
stats
|
342
|
+
end
|
343
|
+
|
344
|
+
# @note This method should be called from inside a {#poll} block.
|
345
|
+
# @param [#receipt_handle] message An object that responds to
|
346
|
+
# `#receipt_handle`.
|
347
|
+
# @param [Integer] seconds
|
348
|
+
def change_message_visibility_timeout(message, seconds)
|
349
|
+
@client.change_message_visibility({
|
350
|
+
queue_url: @queue_url,
|
351
|
+
receipt_handle: message.receipt_handle,
|
352
|
+
visibility_timeout: seconds,
|
353
|
+
})
|
354
|
+
end
|
355
|
+
|
356
|
+
# @note This method should be called from inside a {#poll} block.
|
357
|
+
# @param [#receipt_handle] message An object that responds to
|
358
|
+
# `#receipt_handle`.
|
359
|
+
def delete_message(message)
|
360
|
+
@client.delete_message({
|
361
|
+
queue_url: @queue_url,
|
362
|
+
receipt_handle: message.receipt_handle,
|
363
|
+
})
|
364
|
+
end
|
365
|
+
|
366
|
+
# @note This method should be called from inside a {#poll} block.
|
367
|
+
# @param [Array<#message_id, #receipt_handle>] messages An array of received
|
368
|
+
# messages. Each object must respond to `#message_id` and
|
369
|
+
# `#receipt_handle`.
|
370
|
+
def delete_messages(messages)
|
371
|
+
@client.delete_message_batch(
|
372
|
+
queue_url: @queue_url,
|
373
|
+
entries: messages.map { |msg|
|
374
|
+
{ id: msg.message_id, receipt_handle: msg.receipt_handle }
|
375
|
+
}
|
376
|
+
)
|
377
|
+
end
|
378
|
+
|
379
|
+
private
|
380
|
+
|
381
|
+
def get_messages(config, stats)
|
382
|
+
config.before_request.call(stats) if config.before_request
|
383
|
+
messages = send_request(config).messages
|
384
|
+
stats.request_count += 1
|
385
|
+
messages
|
386
|
+
end
|
387
|
+
|
388
|
+
def send_request(config)
|
389
|
+
params = config.request_params.merge(queue_url: @queue_url)
|
390
|
+
@client.receive_message(params)
|
391
|
+
end
|
392
|
+
|
393
|
+
def check_idle_timeout(config, stats, messages)
|
394
|
+
if config.idle_timeout
|
395
|
+
since = stats.last_message_received_at || stats.polling_started_at
|
396
|
+
idle_time = Time.now - since
|
397
|
+
throw :stop_polling if idle_time > config.idle_timeout
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def process_messages(config, stats, messages, &block)
|
402
|
+
stats.received_message_count += messages.count
|
403
|
+
stats.last_message_received_at = Time.now
|
404
|
+
catch(:skip_delete) do
|
405
|
+
yield_messages(config, messages, stats, &block)
|
406
|
+
delete_messages(messages) unless config.skip_delete
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def yield_messages(config, messages, stats, &block)
|
411
|
+
if config.request_params[:max_number_of_messages] == 1
|
412
|
+
messages.each do |msg|
|
413
|
+
yield(msg, stats)
|
414
|
+
end
|
415
|
+
else
|
416
|
+
yield(messages, stats)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# Statistics tracked client-side by the {QueuePoller}.
|
421
|
+
class PollerStats
|
422
|
+
|
423
|
+
def initialize
|
424
|
+
@request_count = 0
|
425
|
+
@received_message_count = 0
|
426
|
+
@last_message_received_at = nil
|
427
|
+
@polling_started_at = Time.now
|
428
|
+
@polling_stopped_at = nil
|
429
|
+
end
|
430
|
+
|
431
|
+
# @return [Integer]
|
432
|
+
attr_accessor :request_count
|
433
|
+
|
434
|
+
# @return [Integer]
|
435
|
+
attr_accessor :received_message_count
|
436
|
+
|
437
|
+
# @return [Time,nil]
|
438
|
+
attr_accessor :last_message_received_at
|
439
|
+
|
440
|
+
# @return [Time]
|
441
|
+
attr_accessor :polling_started_at
|
442
|
+
|
443
|
+
# @return [Time,nil]
|
444
|
+
attr_accessor :polling_stopped_at
|
445
|
+
|
446
|
+
end
|
447
|
+
|
448
|
+
# A read-only set of configuration used by the QueuePoller.
|
449
|
+
class PollerConfig
|
450
|
+
|
451
|
+
# @api private
|
452
|
+
CONFIG_OPTIONS = Set.new([
|
453
|
+
:idle_timeout,
|
454
|
+
:skip_delete,
|
455
|
+
:before_request,
|
456
|
+
])
|
457
|
+
|
458
|
+
# @api private
|
459
|
+
PARAM_OPTIONS = Set.new([
|
460
|
+
:wait_time_seconds,
|
461
|
+
:max_number_of_messages,
|
462
|
+
:visibility_timeout,
|
463
|
+
:attribute_names,
|
464
|
+
:message_attribute_names,
|
465
|
+
])
|
466
|
+
|
467
|
+
# @return [Integer,nil]
|
468
|
+
attr_reader :idle_timeout
|
469
|
+
|
470
|
+
# @return [Boolean]
|
471
|
+
attr_reader :skip_delete
|
472
|
+
|
473
|
+
# @return [Proc,nil]
|
474
|
+
attr_reader :before_request
|
475
|
+
|
476
|
+
# @return [Hash]
|
477
|
+
attr_reader :request_params
|
478
|
+
|
479
|
+
def initialize(options)
|
480
|
+
@idle_timeout = nil
|
481
|
+
@skip_delete = false
|
482
|
+
@before_request = nil
|
483
|
+
@request_params = {
|
484
|
+
wait_time_seconds: 20,
|
485
|
+
max_number_of_messages: 1,
|
486
|
+
visibility_timeout: nil,
|
487
|
+
attribute_names: ['All'],
|
488
|
+
message_attribute_names: ['All'],
|
489
|
+
}
|
490
|
+
options.each do |opt_name, value|
|
491
|
+
if CONFIG_OPTIONS.include?(opt_name)
|
492
|
+
instance_variable_set("@#{opt_name}", value)
|
493
|
+
elsif PARAM_OPTIONS.include?(opt_name)
|
494
|
+
@request_params[opt_name] = value
|
495
|
+
else
|
496
|
+
raise ArgumentError, "invalid option #{opt_name.inspect}"
|
497
|
+
end
|
498
|
+
end
|
499
|
+
@request_params.freeze
|
500
|
+
freeze
|
501
|
+
end
|
502
|
+
|
503
|
+
# @return [PollerConfig] Returns a new {PollerConfig} instance
|
504
|
+
# with the given options applied.
|
505
|
+
def with(options)
|
506
|
+
self.class.new(to_h.merge(options))
|
507
|
+
end
|
508
|
+
|
509
|
+
private
|
510
|
+
|
511
|
+
def to_h
|
512
|
+
hash = {}
|
513
|
+
CONFIG_OPTIONS.each { |key| hash[key] = send(key) }
|
514
|
+
PARAM_OPTIONS.each { |key| hash[key] = @request_params[key] }
|
515
|
+
hash
|
516
|
+
end
|
517
|
+
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|