aws-sdk-extended 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5018a786a6e45aa31c58ee6064b53545f79116d5aed40b3b2f2ce1f4963d94a7
4
+ data.tar.gz: 22eaa50da7bcb12df4e492105f7ebbd79bf037b8b784716a0f188c01c0f1e5ce
5
+ SHA512:
6
+ metadata.gz: 3bce11973625136b628f685210bf49fe96ef022a5114108bfa2c7916a4c4fa3b9c0dc4a2c9ea08cfc924c521a95285aab0b04d2f91bcab70986510ea319496de
7
+ data.tar.gz: 439772535e96a84427a7d869530b7d938c90cd7ce48706c733ecd18a960e4ecf5addcbe5d3874f8df81b63f9a3276c237a9a787b3de94c92941d98db382c4145
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 HoneyryderChuck
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # Amazon Extended Client Library for Ruby
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/aws-sdk-extended.svg)](http://rubygems.org/gems/aws-sdk-extended)
4
+ [![Build status](https://github.com/HoneyryderChuck/aws-sdk-extended/actions/workflows/ci.yml/badge.svg)](https://github.com/HoneyryderChuck/aws-sdk-extended)
5
+
6
+
7
+ ### Implements the functionality of [amazon-sns-java-extended-client-lib](https://github.com/awslabs/amazon-sns-java-extended-client-lib) and [amazon-sqs-java-extended-client-lib](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-s3-messages.html) in Ruby
8
+
9
+ The Amazon SQS Extended Client allows clients to manage Amazon SNS and SQS message payloads that exceed the 256 KB message size limit, up to a size of 2 GB. In the event of publishing such large messages, the client accomplishes this feat by storing the actual payload in a S3 bucket and by storing the reference of the stored object in the SQS queue. Similarly, the extended-client is also used for retrieving and dereferencing these references of message objects stored in S3. Thus, the library is used for the following purposes:
10
+
11
+ 1. Specify whether payloads are always stored in Amazon S3 or only when a payload's size exceeds 256 KB.
12
+ 2. Send a message that references a single message object stored in an Amazon S3 bucket.
13
+ 3. Get the corresponding payload object from an Amazon S3 bucket.
14
+ 4. Delete the corresponding payload object from an Amazon S3 bucket.
15
+
16
+ ## Installation
17
+
18
+ Install the gem and add to the application's Gemfile by executing:
19
+
20
+ ```bash
21
+ bundle add aws-sdk-extended
22
+ ```
23
+
24
+ If bundler is not being used to manage dependencies, install the gem by executing:
25
+
26
+ ```bash
27
+ gem install aws-sdk-extended
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ The SNS and SQS extended clients are initialized respectively with `Aws::SNS::Client` or `Aws::SQS::Client` objects as single mandatory positional arguments. All method calls are delegated to them, so you can use the extended client objects everywhere you'd use the standard AWS SDK client objects.
33
+
34
+ They can be initialized with the following optional attributes (as keyword arguments):
35
+
36
+ * `:use_legacy_attribute` -- if `true`, then all published messages use the Legacy reserved message attribute (`"SQSLargePayloadSize"`) instead of the current reserved message attribute (`"ExtendedPayloadSize"`) (defaults to `false`)
37
+ * `:bucket` -- the S3 bucket name that will store large messages. It falls back to the bucket identified by the `AWS_EXTENDED_CLIENT_S3_BUCKET` env var (if the env var is not set, then it becomes a mandatory attribute).
38
+ * `:payload_size_threshold` -- the threshold for storing the message in the large messages bucket. Cannot be less than `0` or greater than `262144` (defaults to `262144`).
39
+ * `:always_through_s3` -- if `true`, then all messages will be serialized to S3 (defaults to `false`).
40
+ * `:s3_client` -- the `awk-sdk-s3` `Aws::S3::Client` object to use to store objects to S3. Use this if you want to control the S3 client (for example, custom S3 config or credentials) (defaults to a shared `Aws::S3::Client` on first use).
41
+ * (SQS only) `:delete_payload_from_s3` -- if `true`, deletes the large payload from the S3 bucket when the corresponding SQS message is deleted (defaults to `false`).
42
+
43
+ #### Note:
44
+ > The s3 bucket must already exist prior to usage, and be accessible by whatever credentials you have available
45
+
46
+ ```ruby
47
+ # for SNS
48
+ require "aws-sdk-sns"
49
+ require "aws/sdk/extended"
50
+
51
+ sns = Aws::SNS::Client.new
52
+ ext_sns = Aws::SNS::ExtendedClient.new(sns) # or, Aws::SNS::ExtendedClient.new(sns, payload_size_threshold: 32)
53
+
54
+ ext_sns.publish(
55
+ topic_arn: "TOPIC_ARN",
56
+ message: "This message should be published to S3 if it exceeds the threshold",
57
+ )
58
+
59
+ # for SQS
60
+ require "aws-sdk-sqs"
61
+ require "aws/sdk/extended"
62
+
63
+ sqs = Aws::SQS::Client.new
64
+ ext_sqs = Aws::SQS::ExtendedClient.new(sqs) # or, Aws::SQS::ExtendedClient.new(sqs, payload_size_threshold: 32)
65
+
66
+ queue_url = ext_sqs.get_queue_url(
67
+ queue_name: "demo-preparation-queue"
68
+ ).queue_url
69
+
70
+ # sending message
71
+ large_message = "a" * 300000 # Shall cross the limit of 256 KB
72
+
73
+ ext_sqs.send_message(
74
+ queue_url: queue_url,
75
+ message_body: large_message
76
+ )
77
+
78
+ response = ext_sqs.receive_message(
79
+ queue_url: queue_url,
80
+ )
81
+
82
+ response.messages[0].body #=> "aaaaaaaaa...."
83
+ ```
84
+
85
+ ## Development
86
+
87
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
88
+
89
+ To install this gem onto your local machine, run `bundle install`. To release a new version, update the version number in `version.rb`, add the relevant change description to `CHANGELOG.md`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
90
+
91
+ `aws-sdk-extended` ships with RBS signature files, and uses RBS inline syntax and `rbs-inline` to keep them in sync. In order to not let them fall out of sync when developing, run `fswatch -0 lib | xargs -0 -n1 bundle exec rbs-inline --opt-out --output=sig` in the background.
92
+
93
+ ## Contributing
94
+
95
+ Bug reports and pull requests are welcome on GitHub at https://github.com/HoneyryderChuck/aws-sdk-extended. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/HoneyryderChuck/aws-sdk-extended/blob/master/CODE_OF_CONDUCT.md).
96
+
97
+ ## License
98
+
99
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
100
+
101
+ ## Code of Conduct
102
+
103
+ Everyone interacting in the Aws::Sdk::Extended project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/HoneyryderChuck/aws-sdk-extended/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk-sns"
4
+
5
+ require_relative "../extended"
6
+
7
+ module Aws
8
+ module SNS
9
+ # Amazon SNS extended client, API-compatible with Aws::SNS::Client from the `aws-sdk-sns` gem,
10
+ # invokes service methods on Amazon SNS with extended functionality to support large payloads.
11
+ class ExtendedClient < SimpleDelegator
12
+ include Extended
13
+
14
+ attr_reader :sns_client #: SNS::Client
15
+
16
+ attr_reader :s3_client #: S3::Client
17
+
18
+ attr_reader :bucket #: String
19
+
20
+ attr_reader :always_through_s3 #: bool
21
+
22
+ attr_reader :payload_size_threshold #: Integer
23
+
24
+ # Constructs a new SNS extended client, where all SNS service calls are forwarded to
25
+ # +sns_client+, and calls to S3 to store large payloads are forwarded to +s3_client+ using the bucket
26
+ # specified by +bucket+, and not just payloads which are bigger than the number of bytes specified by
27
+ # +payload_size_threshold+. This can be circumvented by +always_through_s3+ though, which will cause it
28
+ # to store all payloads in the S3 bucket.
29
+ #: (
30
+ #| SNS::Client,
31
+ #| ?bucket: String,
32
+ #| ?s3_client: S3::Client,
33
+ #| ?always_through_s3: bool,
34
+ #| ?payload_size_threshold) -> void
35
+ def initialize(
36
+ sns_client,
37
+ bucket: ENV.fetch("AWS_EXTENDED_CLIENT_S3_BUCKET"),
38
+ s3_client: Extended.default_s3_client,
39
+ always_through_s3: false,
40
+ payload_size_threshold: MESSAGE_SIZE_THRESHOLD
41
+ )
42
+ @sns_client = sns_client
43
+ @s3_client = s3_client
44
+ @bucket = bucket
45
+ @always_through_s3 = always_through_s3
46
+ @payload_size_threshold = payload_size_threshold
47
+ super(sns_client)
48
+ end
49
+
50
+ def freeze #: void
51
+ @sns_client.freeze
52
+ @s3_client.freeze
53
+ @bucket.freeze
54
+ super
55
+ end
56
+
57
+ # Same functionality as {Aws::SNS::Client#publish}, will store the message payload in S3
58
+ # if all conditions match.
59
+ #: (Hash[Symbol, untyped], *untyped) -> SNS::Client::_PublishResponseSuccess
60
+ def publish(params = {}, *)
61
+ input = Aws::SNS::Types::PublishInput.new(params)
62
+
63
+ return super unless input.message && (@always_through_s3 || large_message?(input))
64
+
65
+ # TODO: raise error for JSON payloads: https://github.com/awslabs/amazon-sns-java-extended-client-lib/blob/main/src/main/java/software/amazon/sns/AmazonSNSExtendedClient.java#L184
66
+ if input.message_structure == "json"
67
+ raise Extended::Error, "SNS extended client does not support sending JSON messages"
68
+ end
69
+
70
+ params[:message_attributes], params[:message] =
71
+ Extended.store_large_message(
72
+ input.message_attributes,
73
+ input.message, @s3_client, @bucket
74
+ )
75
+
76
+ super
77
+ end
78
+
79
+ private
80
+
81
+ # returns whether +input+ is a large message.
82
+ #
83
+ #: (SNS::Types::PublishInput) -> bool
84
+ def large_message?(input)
85
+ @payload_size_threshold < Extended.message_size(input.message_attributes, input.message)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk-sqs"
4
+
5
+ require_relative "../extended"
6
+
7
+ module Aws
8
+ module SQS
9
+ # Amazon SQS extended client, API-compatible with Aws::SQS::Client from the `aws-sdk-sqs` gem,
10
+ # invokes service methods on Amazon SQS with extended functionality to support large payloads.
11
+ class ExtendedClient < SimpleDelegator
12
+ include Extended
13
+
14
+ attr_reader :sqs_client #: SQS::Client
15
+
16
+ attr_reader :s3_client #: S3::Client
17
+
18
+ attr_reader :bucket #: String
19
+
20
+ attr_reader :always_through_s3 #: bool
21
+
22
+ attr_reader :delete_payload_from_s3 #: bool
23
+
24
+ attr_reader :payload_size_threshold #: Integer
25
+
26
+ # Constructs a new SQS extended client, where all SQS service calls are forwarded to
27
+ # +sqs_client+, and calls to S3 to store and retrieve large payloads are forwarded to +s3_client+
28
+ # using the bucket specified by +bucket+, and not just message bodies which are bigger than the number of
29
+ # bytes specified by +payload_size_threshold+. This can be circumvented by +always_through_s3+ though,
30
+ # which will cause it to store all message bodies in the S3 bucket.
31
+ #
32
+ # When the message is deleted, +delete_payload_from_s3+ determines whether the large payload stored in the
33
+ # S3 bucket should be deleted alongside.
34
+ #
35
+ #: (
36
+ #| SQS::Client,
37
+ #| ?bucket: String,
38
+ #| ?s3_client: S3::Client,
39
+ #| ?always_through_s3: bool,
40
+ #| delete_payload_from_s3: bool,
41
+ #| ?payload_size_threshold) -> void
42
+ def initialize(
43
+ sqs_client,
44
+ bucket: ENV.fetch("AWS_EXTENDED_CLIENT_S3_BUCKET"),
45
+ s3_client: Extended.default_s3_client,
46
+ always_through_s3: false,
47
+ delete_payload_from_s3: false,
48
+ payload_size_threshold: Extended::MESSAGE_SIZE_THRESHOLD
49
+ )
50
+ @sqs_client = sqs_client
51
+ @s3_client = s3_client
52
+ @bucket = bucket
53
+ @always_through_s3 = always_through_s3
54
+ @delete_payload_from_s3 = delete_payload_from_s3
55
+ @payload_size_threshold = payload_size_threshold
56
+ super(sqs_client)
57
+ end
58
+
59
+ def freeze
60
+ @sqs_client.freeze
61
+ @s3_client.freeze
62
+ @bucket.freeze
63
+ super
64
+ end
65
+
66
+ # Same functionality as {Aws::SQS::Client#send_message}, will store the message body in S3
67
+ # if all conditions match.
68
+ #
69
+ #: (Hash[Symbol, untyped], *untyped) -> SQS::Client::_SendMessageResponseSuccess
70
+ def send_message(params = {}, *)
71
+ request = Aws::SQS::Types::SendMessageRequest.new(params)
72
+
73
+ return super unless request.queue_url && request.message_body && (@always_through_s3 || large_message?(request))
74
+
75
+ params[:message_attributes], params[:message_body] =
76
+ Extended.store_large_message(
77
+ request.message_attributes,
78
+ request.message_body, @s3_client, @bucket
79
+ )
80
+
81
+ super
82
+ end
83
+
84
+ # Same functionality as {Aws::SQS::Client#send_message_batch}, will store the message bodies in S3
85
+ # if all conditions match, and store the reference in the SQS message.
86
+ #
87
+ #: (Hash[Symbol, untyped], *untyped) -> SQS::Client::_SendMessageBatchResponseSuccess
88
+ def send_message_batch(params = {}, options = {})
89
+ request = Aws::SQS::Types::SendMessageBatchRequest.new(params)
90
+
91
+ return super unless request.queue_url && request.entries
92
+
93
+ request.entries.each do |entry|
94
+ request = Aws::SQS::Types::SendMessageBatchRequestEntry.new(entry)
95
+
96
+ next unless @always_through_s3 || large_message?(request)
97
+
98
+ Extended.store_large_message(request.message_attributes, request.message_body, @s3_client, @bucket)
99
+ end
100
+
101
+ super
102
+ end
103
+
104
+ # Same functionality as {Aws::SQS::Client#receive_message}, will transparently retrieve large payloads
105
+ # and store them as the message object in the returned object.
106
+ #
107
+ #: (Hash[Symbol, untyped], *untyped) -> SQS::Client::_ReceiveMessageResponseSuccess
108
+ def receive_message(params = {}, *)
109
+ requested_attributes = params.fetch(:message_attribute_names, [])
110
+
111
+ unless requested_attributes.include?("All") ||
112
+ requested_attributes.include?("*")
113
+ requested_attributes << RESERVED_ATTRIBUTE_NAME unless requested_attributes.include?(RESERVED_ATTRIBUTE_NAME)
114
+
115
+ unless requested_attributes.include?(LEGACY_RESERVED_ATTRIBUTE_NAME)
116
+ requested_attributes << LEGACY_RESERVED_ATTRIBUTE_NAME
117
+ end
118
+ end
119
+
120
+ params[:message_attribute_names] = requested_attributes
121
+
122
+ super.tap do |resp|
123
+ messages = resp.messages || EMPTY_ARRAY #: Array[Aws::SQS::Types::Message]
124
+
125
+ messages.each do |message|
126
+ message_attributes = message.message_attributes
127
+
128
+ next unless message_attributes && (
129
+ message_attributes.include?(RESERVED_ATTRIBUTE_NAME) ||
130
+ message_attributes.include?(LEGACY_RESERVED_ATTRIBUTE_NAME)
131
+ )
132
+
133
+ get_large_message(message)
134
+
135
+ message_attributes.delete(RESERVED_ATTRIBUTE_NAME)
136
+ message_attributes.delete(LEGACY_RESERVED_ATTRIBUTE_NAME)
137
+ end
138
+ end
139
+ end
140
+
141
+ # Same functionality as {Aws::SQS::Client#delete_message}, will (if +@delete_payload_froms3+) delete
142
+ # the respective s3 objects from the S3 bucket.
143
+ # and store them as the message object in the returned object.
144
+ #
145
+ #: (Hash[Symbol, untyped], *untyped) -> Seahorse::Client::_ResponseSuccess[EmptyStructure]
146
+ def delete_message(params = {}, *)
147
+ receipt_handle = params[:receipt_handle]
148
+
149
+ return super unless @delete_payload_from_s3 && receipt_handle && large_message_receipt_handle?(receipt_handle)
150
+
151
+ bucket, key, params[:receipt_handle] = bucket_key_original_receipt_handle(receipt_handle)
152
+
153
+ # TODO: what to do with error?
154
+ @s3_client.delete_object(bucket: bucket, key: key)
155
+
156
+ super
157
+ end
158
+
159
+ # Same functionality as {Aws::SQS::Client#delete_message_batch}, will (if +@delete_payload_froms3+) delete
160
+ # the respective s3 objects from the S3 bucket.
161
+ #
162
+ #: (Hash[Symbol, untyped], *untyped) -> SQS::Client::_DeleteMessageBatchResponseSuccess
163
+ def delete_message_batch(params = {}, *)
164
+ request = Aws::SQS::Types::DeleteMessageBatchRequest.new(params)
165
+
166
+ if request.queue_url && request.entries
167
+ s3_to_delete = request.entries.map do |entry|
168
+ receipt_handle = entry[:receipt_handle]
169
+ next unless @delete_payload_from_s3 && receipt_handle && large_message_receipt_handle?(receipt_handle)
170
+
171
+ *bucket_key, entry[:receipt_handle] = bucket_key_original_receipt_handle(receipt_handle)
172
+
173
+ bucket_key
174
+ end
175
+
176
+ s3_to_delete.each do |bucket, key|
177
+ # TODO: what to do with error?
178
+ @s3_client.delete_object(bucket: bucket, key: key)
179
+ end
180
+ end
181
+
182
+ super
183
+ end
184
+
185
+ # Same functionality as {Aws::SQS::Client#change_message_visibility}
186
+ #
187
+ #: (Hash[Symbol, untyped], *untyped) -> Seahorse::Client::_ResponseSuccess[EmptyStructure]
188
+ def change_message_visibility(params = {}, *)
189
+ request = Aws::SQS::Types::ChangeMessageVisibilityRequest.new(params)
190
+
191
+ if request.queue_url && request.receipt_handle &&
192
+ request.visibility_timeout && large_message_receipt_handle?(request.receipt_handle)
193
+
194
+ *_, params[:receipt_handle] = bucket_key_original_receipt_handle(request.receipt_handle)
195
+ end
196
+
197
+ super
198
+ end
199
+
200
+ private
201
+
202
+ #: (Aws::SQS::Types::SendMessageRequest | Aws::SQS::Types::SendMessageBatchRequestEntry) -> bool
203
+ def large_message?(request)
204
+ @payload_size_threshold < Extended.message_size(request.message_attributes, request.message_body)
205
+ end
206
+
207
+ #: (String) -> bool
208
+ def large_message_receipt_handle?(receipt_handle)
209
+ receipt_handle.start_with?(S3_BUCKET_NAME_MARKER)
210
+ end
211
+
212
+ #: (Aws::SQS::Types::Message) -> void
213
+ def get_large_message(message)
214
+ receipt_handle = message.receipt_handle
215
+
216
+ body = JSON.parse(message.body)
217
+
218
+ unless body.is_a?(Array) && body.size == 2 &&
219
+ (_, s3_params = body) && s3_params.is_a?(Hash)
220
+ raise Extended::Error,
221
+ "Invalid payload format for retrieving stored messages in S3"
222
+ end
223
+
224
+ bucket, key = s3_params.values_at("s3BucketName", "s3Key")
225
+
226
+ message.receipt_handle = format(RECEIPT_HANDLE_FORMAT, bucket, key, receipt_handle)
227
+
228
+ resp = @s3_client.get_object(bucket: bucket, key: key)
229
+
230
+ message.body = resp.body.read
231
+ end
232
+
233
+ #: (String) -> [String, String, String]
234
+ def bucket_key_original_receipt_handle(receipt_handle)
235
+ _, bucket, = receipt_handle.split(S3_BUCKET_NAME_MARKER)
236
+ _, key, original_receipt_handle = receipt_handle.split(S3_KEY_MARKER)
237
+
238
+ [bucket, key, original_receipt_handle] #: [String, String, String]
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module Sdk
5
+ module Extended
6
+ VERSION = "0.0.1"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "securerandom"
5
+ require "delegate"
6
+ require "aws-sdk-s3"
7
+
8
+ require_relative "extended/version"
9
+
10
+ module Aws
11
+ module Extended
12
+ # @rbs!
13
+ # interface _WithSize
14
+ # def size: () -> Integer
15
+ # end
16
+
17
+ EMPTY_HASH = {}.freeze #: Hash[untyped, untyped]
18
+ EMPTY_ARRAY = [].freeze #: Array[untyped]
19
+ EMPTY_STRING = ""
20
+ MESSAGE_SIZE_THRESHOLD = 262_144
21
+ MAX_ALLOWED_ATTRIBUTES = 10
22
+ LEGACY_RESERVED_ATTRIBUTE_NAME = "SQSLargePayloadSize"
23
+ RESERVED_ATTRIBUTE_NAME = "ExtendedPayloadSize"
24
+ S3_KEY_ATTRIBUTE_NAME = "S3Key"
25
+ S3_BUCKET_NAME_MARKER = "-..s3BucketName..-"
26
+ S3_KEY_MARKER = "-..s3Key..-"
27
+ RECEIPT_HANDLE_FORMAT = "#{S3_BUCKET_NAME_MARKER}%s" \
28
+ "#{S3_BUCKET_NAME_MARKER}#{S3_KEY_MARKER}%s" \
29
+ "#{S3_KEY_MARKER}%s".freeze
30
+
31
+ DEFAULT_S3_MUTEX = Mutex.new
32
+
33
+ class Error < StandardError
34
+ end
35
+
36
+ module_function
37
+
38
+ # @rbs self.@default_s3_client: Aws::S3::Client
39
+
40
+ def default_s3_client #: Aws::S3::Client
41
+ @default_s3_client || DEFAULT_S3_MUTEX.synchronize do
42
+ @default_s3_client ||= Aws::S3::Client.new
43
+ end
44
+ end
45
+
46
+ #: (Hash[String, untyped], _WithSize) -> Integer
47
+ def message_size(message_attributes, body)
48
+ total_size = body.size
49
+
50
+ if message_attributes
51
+ total_size = message_attributes.sum(total_size) do |key, value|
52
+ key.size + value.fetch(:data_type, EMPTY_STRING).size +
53
+ value.fetch(:binary_value, EMPTY_STRING).size +
54
+ value.fetch(:string_value, EMPTY_STRING).size
55
+ end
56
+ end
57
+
58
+ total_size
59
+ end
60
+
61
+ #: (Hash[String, untyped], _WithSize, S3::Client, String) -> [Hash[Symbol, untyped], String]
62
+ def store_large_message(message_attributes, body, s3_client, bucket)
63
+ message_attributes ||= Extended::EMPTY_HASH
64
+
65
+ if message_attributes.size > Extended::MAX_ALLOWED_ATTRIBUTES - 1
66
+ raise Extended::Error,
67
+ "Number of message attributes exceeds the maximum allowed for large payload messages"
68
+ end
69
+
70
+ if message_attributes.key?(Extended::RESERVED_ATTRIBUTE_NAME) ||
71
+ message_attributes.key?(Extended::LEGACY_RESERVED_ATTRIBUTE_NAME)
72
+ raise Extended::Error,
73
+ "Use of an attribute reserved by SQS extended client."
74
+ end
75
+
76
+ s3_key = message_attributes.fetch(Extended::S3_KEY_ATTRIBUTE_NAME, SecureRandom.urlsafe_base64)
77
+
78
+ message_attributes = message_attributes.merge(
79
+ # TODO: optionally use legacy attribute
80
+ Extended::RESERVED_ATTRIBUTE_NAME => {
81
+ data_type: "Number",
82
+ string_value: body.size.to_s
83
+ },
84
+ Extended::S3_KEY_ATTRIBUTE_NAME => {
85
+ data_type: "String",
86
+ string_value: s3_key
87
+ }
88
+ )
89
+
90
+ s3_client.put_object(bucket: bucket, key: s3_key, body: body)
91
+
92
+ message_body = JSON.generate(
93
+ [
94
+ Extended::RESERVED_ATTRIBUTE_NAME,
95
+ { s3BucketName: bucket, s3Key: s3_key }
96
+ ]
97
+ )
98
+
99
+ [message_attributes, message_body]
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,48 @@
1
+ # Generated from lib/aws/sdk/extended/sns.rb with RBS::Inline
2
+
3
+ module Aws
4
+ module SNS
5
+ # Amazon SNS extended client, API-compatible with Aws::SNS::Client from the `aws-sdk-sns` gem,
6
+ # invokes service methods on Amazon SNS with extended functionality to support large payloads.
7
+ class ExtendedClient < SimpleDelegator
8
+ include Extended
9
+
10
+ attr_reader sns_client: SNS::Client
11
+
12
+ attr_reader s3_client: S3::Client
13
+
14
+ attr_reader bucket: String
15
+
16
+ attr_reader always_through_s3: bool
17
+
18
+ attr_reader payload_size_threshold: Integer
19
+
20
+ # Constructs a new SNS extended client, where all SNS service calls are forwarded to
21
+ # +sns_client+, and calls to S3 to store large payloads are forwarded to +s3_client+ using the bucket
22
+ # specified by +bucket+, and not just payloads which are bigger than the number of bytes specified by
23
+ # +payload_size_threshold+. This can be circumvented by +always_through_s3+ though, which will cause it
24
+ # to store all payloads in the S3 bucket.
25
+ # : (
26
+ # | SNS::Client,
27
+ # | ?bucket: String,
28
+ # | ?s3_client: S3::Client,
29
+ # | ?always_through_s3: bool,
30
+ # | ?payload_size_threshold) -> void
31
+ def initialize: (untyped sns_client, ?bucket: untyped, ?s3_client: untyped, ?always_through_s3: untyped, ?payload_size_threshold: untyped) -> untyped
32
+
33
+ def freeze: () -> void
34
+
35
+ # Same functionality as {Aws::SNS::Client#publish}, will store the message payload in S3
36
+ # if all conditions match.
37
+ # : (Hash[Symbol, untyped], *untyped) -> SNS::Client::_PublishResponseSuccess
38
+ def publish: (Hash[Symbol, untyped], *untyped) -> SNS::Client::_PublishResponseSuccess
39
+
40
+ private
41
+
42
+ # returns whether +input+ is a large message.
43
+ #
44
+ # : (SNS::Types::PublishInput) -> bool
45
+ def large_message?: (SNS::Types::PublishInput) -> bool
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,93 @@
1
+ # Generated from lib/aws/sdk/extended/sqs.rb with RBS::Inline
2
+
3
+ module Aws
4
+ module SQS
5
+ # Amazon SQS extended client, API-compatible with Aws::SQS::Client from the `aws-sdk-sqs` gem,
6
+ # invokes service methods on Amazon SQS with extended functionality to support large payloads.
7
+ class ExtendedClient < SimpleDelegator
8
+ include Extended
9
+
10
+ attr_reader sqs_client: SQS::Client
11
+
12
+ attr_reader s3_client: S3::Client
13
+
14
+ attr_reader bucket: String
15
+
16
+ attr_reader always_through_s3: bool
17
+
18
+ attr_reader delete_payload_from_s3: bool
19
+
20
+ attr_reader payload_size_threshold: Integer
21
+
22
+ # Constructs a new SQS extended client, where all SQS service calls are forwarded to
23
+ # +sqs_client+, and calls to S3 to store and retrieve large payloads are forwarded to +s3_client+
24
+ # using the bucket specified by +bucket+, and not just message bodies which are bigger than the number of
25
+ # bytes specified by +payload_size_threshold+. This can be circumvented by +always_through_s3+ though,
26
+ # which will cause it to store all message bodies in the S3 bucket.
27
+ #
28
+ # When the message is deleted, +delete_payload_from_s3+ determines whether the large payload stored in the
29
+ # S3 bucket should be deleted alongside.
30
+ #
31
+ # : (
32
+ # | SQS::Client,
33
+ # | ?bucket: String,
34
+ # | ?s3_client: S3::Client,
35
+ # | ?always_through_s3: bool,
36
+ # | delete_payload_from_s3: bool,
37
+ # | ?payload_size_threshold) -> void
38
+ def initialize: (untyped sqs_client, ?bucket: untyped, ?s3_client: untyped, ?always_through_s3: untyped, ?delete_payload_from_s3: untyped, ?payload_size_threshold: untyped) -> untyped
39
+
40
+ def freeze: () -> untyped
41
+
42
+ # Same functionality as {Aws::SQS::Client#send_message}, will store the message body in S3
43
+ # if all conditions match.
44
+ #
45
+ # : (Hash[Symbol, untyped], *untyped) -> SQS::Client::_SendMessageResponseSuccess
46
+ def send_message: (Hash[Symbol, untyped], *untyped) -> SQS::Client::_SendMessageResponseSuccess
47
+
48
+ # Same functionality as {Aws::SQS::Client#send_message_batch}, will store the message bodies in S3
49
+ # if all conditions match, and store the reference in the SQS message.
50
+ #
51
+ # : (Hash[Symbol, untyped], *untyped) -> SQS::Client::_SendMessageBatchResponseSuccess
52
+ def send_message_batch: (Hash[Symbol, untyped], *untyped) -> SQS::Client::_SendMessageBatchResponseSuccess
53
+
54
+ # Same functionality as {Aws::SQS::Client#receive_message}, will transparently retrieve large payloads
55
+ # and store them as the message object in the returned object.
56
+ #
57
+ # : (Hash[Symbol, untyped], *untyped) -> SQS::Client::_ReceiveMessageResponseSuccess
58
+ def receive_message: (Hash[Symbol, untyped], *untyped) -> SQS::Client::_ReceiveMessageResponseSuccess
59
+
60
+ # Same functionality as {Aws::SQS::Client#delete_message}, will (if +@delete_payload_froms3+) delete
61
+ # the respective s3 objects from the S3 bucket.
62
+ # and store them as the message object in the returned object.
63
+ #
64
+ # : (Hash[Symbol, untyped], *untyped) -> Seahorse::Client::_ResponseSuccess[EmptyStructure]
65
+ def delete_message: (Hash[Symbol, untyped], *untyped) -> Seahorse::Client::_ResponseSuccess[EmptyStructure]
66
+
67
+ # Same functionality as {Aws::SQS::Client#delete_message_batch}, will (if +@delete_payload_froms3+) delete
68
+ # the respective s3 objects from the S3 bucket.
69
+ #
70
+ # : (Hash[Symbol, untyped], *untyped) -> SQS::Client::_DeleteMessageBatchResponseSuccess
71
+ def delete_message_batch: (Hash[Symbol, untyped], *untyped) -> SQS::Client::_DeleteMessageBatchResponseSuccess
72
+
73
+ # Same functionality as {Aws::SQS::Client#change_message_visibility}
74
+ #
75
+ # : (Hash[Symbol, untyped], *untyped) -> Seahorse::Client::_ResponseSuccess[EmptyStructure]
76
+ def change_message_visibility: (Hash[Symbol, untyped], *untyped) -> Seahorse::Client::_ResponseSuccess[EmptyStructure]
77
+
78
+ private
79
+
80
+ # : (Aws::SQS::Types::SendMessageRequest | Aws::SQS::Types::SendMessageBatchRequestEntry) -> bool
81
+ def large_message?: (Aws::SQS::Types::SendMessageRequest | Aws::SQS::Types::SendMessageBatchRequestEntry) -> bool
82
+
83
+ # : (String) -> bool
84
+ def large_message_receipt_handle?: (String) -> bool
85
+
86
+ # : (Aws::SQS::Types::Message) -> void
87
+ def get_large_message: (Aws::SQS::Types::Message) -> void
88
+
89
+ # : (String) -> [String, String, String]
90
+ def bucket_key_original_receipt_handle: (String) -> [ String, String, String ]
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,9 @@
1
+ # Generated from lib/aws/sdk/extended/version.rb with RBS::Inline
2
+
3
+ module Aws
4
+ module Sdk
5
+ module Extended
6
+ VERSION: ::String
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ # Generated from lib/aws/sdk/extended.rb with RBS::Inline
2
+
3
+ module Aws
4
+ module Extended
5
+ interface _WithSize
6
+ def size: () -> Integer
7
+ end
8
+
9
+ EMPTY_HASH: Hash[untyped, untyped]
10
+
11
+ EMPTY_ARRAY: Array[untyped]
12
+
13
+ EMPTY_STRING: ::String
14
+
15
+ MESSAGE_SIZE_THRESHOLD: ::Integer
16
+
17
+ MAX_ALLOWED_ATTRIBUTES: ::Integer
18
+
19
+ LEGACY_RESERVED_ATTRIBUTE_NAME: ::String
20
+
21
+ RESERVED_ATTRIBUTE_NAME: ::String
22
+
23
+ S3_KEY_ATTRIBUTE_NAME: ::String
24
+
25
+ S3_BUCKET_NAME_MARKER: ::String
26
+
27
+ S3_KEY_MARKER: ::String
28
+
29
+ RECEIPT_HANDLE_FORMAT: untyped
30
+
31
+ DEFAULT_S3_MUTEX: untyped
32
+
33
+ class Error < StandardError
34
+ end
35
+
36
+ self.@default_s3_client: Aws::S3::Client
37
+
38
+ def self?.default_s3_client: () -> Aws::S3::Client
39
+
40
+ # : (Hash[String, untyped], _WithSize) -> Integer
41
+ def self?.message_size: (Hash[String, untyped], _WithSize) -> Integer
42
+
43
+ # : (Hash[String, untyped], _WithSize, S3::Client, String) -> [Hash[Symbol, untyped], String]
44
+ def self?.store_large_message: (Hash[String, untyped], _WithSize, S3::Client, String) -> [ Hash[Symbol, untyped], String ]
45
+ end
46
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-sdk-extended
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tiago Cardoso
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: aws-sdk-s3
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: |2
27
+ Ships with API-compatible clients which support for (SNS) publishing, (SQS) sending, receiving,
28
+ deleting (and more) messages which exceed the threshold of 256KB imposed by AWS services (up to 2GB).
29
+ email:
30
+ - cardoso_tiago@hotmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - LICENSE.txt
36
+ - README.md
37
+ - lib/aws/sdk/extended.rb
38
+ - lib/aws/sdk/extended/sns.rb
39
+ - lib/aws/sdk/extended/sqs.rb
40
+ - lib/aws/sdk/extended/version.rb
41
+ - sig/aws/sdk/extended.rbs
42
+ - sig/aws/sdk/extended/sns.rbs
43
+ - sig/aws/sdk/extended/sqs.rbs
44
+ - sig/aws/sdk/extended/version.rbs
45
+ homepage: https://github.com/HoneyryderChuck/aws-sdk-extended
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ bug_tracker_uri: https://github.com/igrigorik/http-2/issues
50
+ allowed_push_host: https://rubygems.org
51
+ homepage_uri: https://github.com/HoneyryderChuck/aws-sdk-extended
52
+ source_code_uri: https://github.com/HoneyryderChuck/aws-sdk-extended
53
+ changelog_uri: https://github.com/HoneyryderChuck/aws-sdk-extended/blob/main/CHANGELOG.md
54
+ rubygems_mfa_required: 'true'
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 3.2.0
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubygems_version: 3.6.9
70
+ specification_version: 4
71
+ summary: Ruby version of the AWS SNS and SQS extended clients supporting large payload
72
+ messages
73
+ test_files: []