activemessaging 0.13.4 → 0.14.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -0
- data/Appraisals +1 -0
- data/activemessaging.gemspec +1 -1
- data/gemfiles/activesupport23.gemfile +1 -0
- data/lib/activemessaging/adapters/aws4_signer.rb +110 -0
- data/lib/activemessaging/adapters/sqs.rb +479 -0
- data/lib/activemessaging/version.rb +1 -1
- metadata +118 -100
- checksums.yaml +0 -7
data/.travis.yml
CHANGED
data/Appraisals
CHANGED
data/activemessaging.gemspec
CHANGED
@@ -21,8 +21,8 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.add_development_dependency "bundler"
|
23
23
|
spec.add_development_dependency "test-unit"
|
24
|
-
spec.add_development_dependency "stomp"
|
25
24
|
spec.add_development_dependency "appraisal"
|
25
|
+
spec.add_development_dependency "stomp", "< 1.4"
|
26
26
|
|
27
27
|
spec.add_runtime_dependency "activesupport"
|
28
28
|
spec.add_runtime_dependency "rake"
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# https://github.com/cmdrkeene/aws4
|
2
|
+
#
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013 Brandon Keene
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
#
|
9
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
10
|
+
#
|
11
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
12
|
+
|
13
|
+
# encoding: UTF-8
|
14
|
+
require "openssl"
|
15
|
+
require "time"
|
16
|
+
require "uri"
|
17
|
+
require "pathname"
|
18
|
+
|
19
|
+
class AWS4Signer
|
20
|
+
RFC8601BASIC = "%Y%m%dT%H%M%SZ"
|
21
|
+
attr_reader :access_key, :secret_key, :region
|
22
|
+
attr_reader :date, :method, :uri, :headers, :body, :service
|
23
|
+
|
24
|
+
def initialize(config)
|
25
|
+
@access_key = config[:access_key] || config["access_key"]
|
26
|
+
@secret_key = config[:secret_key] || config["secret_key"]
|
27
|
+
@region = config[:region] || config["region"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def sign(method, uri, headers, body = nil, debug = false, service_name=nil)
|
31
|
+
@method = method.upcase
|
32
|
+
@uri = uri
|
33
|
+
@headers = headers
|
34
|
+
@body = body
|
35
|
+
@service = service_name || @uri.host.split(".", 2)[0]
|
36
|
+
date_header = headers["Date"] || headers["DATE"] || headers["date"]
|
37
|
+
@date = (date_header ? Time.parse(date_header) : Time.now).utc.strftime(RFC8601BASIC)
|
38
|
+
dump if debug
|
39
|
+
signed = headers.dup
|
40
|
+
signed['Authorization'] = authorization(headers)
|
41
|
+
signed
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def authorization(headers)
|
47
|
+
[
|
48
|
+
"AWS4-HMAC-SHA256 Credential=#{access_key}/#{credential_string}",
|
49
|
+
"SignedHeaders=#{headers.keys.map(&:downcase).sort.join(";")}",
|
50
|
+
"Signature=#{signature}"
|
51
|
+
].join(', ')
|
52
|
+
end
|
53
|
+
|
54
|
+
def signature
|
55
|
+
k_date = hmac("AWS4" + secret_key, date[0,8])
|
56
|
+
k_region = hmac(k_date, region)
|
57
|
+
k_service = hmac(k_region, service)
|
58
|
+
k_credentials = hmac(k_service, "aws4_request")
|
59
|
+
hexhmac(k_credentials, string_to_sign)
|
60
|
+
end
|
61
|
+
|
62
|
+
def string_to_sign
|
63
|
+
[
|
64
|
+
'AWS4-HMAC-SHA256',
|
65
|
+
date,
|
66
|
+
credential_string,
|
67
|
+
hexdigest(canonical_request)
|
68
|
+
].join("\n")
|
69
|
+
end
|
70
|
+
|
71
|
+
def credential_string
|
72
|
+
[
|
73
|
+
date[0,8],
|
74
|
+
region,
|
75
|
+
service,
|
76
|
+
"aws4_request"
|
77
|
+
].join("/")
|
78
|
+
end
|
79
|
+
|
80
|
+
def canonical_request
|
81
|
+
[
|
82
|
+
method,
|
83
|
+
Pathname.new(uri.path).cleanpath.to_s,
|
84
|
+
uri.query,
|
85
|
+
headers.sort.map {|k, v| [k.downcase,v.strip].join(':')}.join("\n") + "\n",
|
86
|
+
headers.sort.map {|k, v| k.downcase}.join(";"),
|
87
|
+
hexdigest(body || '')
|
88
|
+
].join("\n")
|
89
|
+
end
|
90
|
+
|
91
|
+
def hexdigest(value)
|
92
|
+
Digest::SHA256.new.update(value).hexdigest
|
93
|
+
end
|
94
|
+
|
95
|
+
def hmac(key, value)
|
96
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
|
97
|
+
end
|
98
|
+
|
99
|
+
def hexhmac(key, value)
|
100
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value)
|
101
|
+
end
|
102
|
+
|
103
|
+
def dump
|
104
|
+
puts "string to sign"
|
105
|
+
puts string_to_sign
|
106
|
+
puts "canonical_request"
|
107
|
+
puts canonical_request
|
108
|
+
puts "authorization"
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,479 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'openssl'
|
5
|
+
require 'base64'
|
6
|
+
require 'cgi'
|
7
|
+
require 'time'
|
8
|
+
require 'uri'
|
9
|
+
require 'rexml/document'
|
10
|
+
require 'erb'
|
11
|
+
|
12
|
+
require 'activemessaging/adapters/base'
|
13
|
+
require 'activemessaging/adapters/aws4_signer'
|
14
|
+
|
15
|
+
module ActiveMessaging
|
16
|
+
module Adapters
|
17
|
+
module Sqs
|
18
|
+
|
19
|
+
class Connection < ActiveMessaging::Adapters::BaseConnection
|
20
|
+
register :sqs
|
21
|
+
|
22
|
+
QUEUE_NAME_LENGTH = 1..80
|
23
|
+
VISIBILITY_TIMEOUT = 0..(24 * 60 * 60)
|
24
|
+
NUMBER_OF_MESSAGES = 1..255
|
25
|
+
GET_QUEUE_ATTRIBUTES = ['All', 'ApproximateNumberOfMessages', 'ApproximateNumberOfMessagesDelayed', 'ApproximateNumberOfMessagesNotVisible', 'CreatedTimestamp', 'DelaySeconds', 'LastModifiedTimestamp', 'MaximumMessageSize', 'MessageRetentionPeriod', 'Policy', 'QueueArn', 'ReceiveMessageWaitTimeSeconds', 'RedrivePolicy', 'VisibilityTimeout', 'KmsMasterKeyId', 'KmsDataKeyReusePeriodSeconds', 'FifoQueue', 'ContentBasedDeduplication']
|
26
|
+
SET_QUEUE_ATTRIBUTES = ['DelaySeconds', 'MaximumMessageSize', 'MessageRetentionPeriod', 'Policy', 'ReceiveMessageWaitTimeSeconds', 'RedrivePolicy', 'VisibilityTimeout', 'KmsMasterKeyId', 'KmsDataKeyReusePeriodSeconds', 'ContentBasedDeduplication']
|
27
|
+
|
28
|
+
#configurable params
|
29
|
+
attr_accessor :reconnect_delay, :access_key_id, :secret_access_key, :aws_version, :content_type, :host, :port, :poll_interval, :cache_queue_list, :max_message_size
|
30
|
+
|
31
|
+
#generic init method needed by a13g
|
32
|
+
def initialize cfg
|
33
|
+
raise "Must specify a access_key_id" if (cfg[:access_key_id].nil? || cfg[:access_key_id].empty?)
|
34
|
+
raise "Must specify a secret_access_key" if (cfg[:secret_access_key].nil? || cfg[:secret_access_key].empty?)
|
35
|
+
|
36
|
+
@access_key_id = cfg[:access_key_id]
|
37
|
+
@secret_access_key = cfg[:secret_access_key]
|
38
|
+
@region = cfg[:region] || 'us-east-1'
|
39
|
+
@request_expires = cfg[:requestExpires] || 10
|
40
|
+
@request_retry_count = cfg[:requestRetryCount] || 5
|
41
|
+
@aws_version = cfg[:aws_version] || '2012-11-05'
|
42
|
+
@content_type = cfg[:content_type] || 'text/plain'
|
43
|
+
@host = cfg[:host] || "sqs.#{@region}.amazonaws.com"
|
44
|
+
@port = cfg[:port] || 80
|
45
|
+
@protocol = cfg[:protocol] || 'http'
|
46
|
+
@poll_interval = cfg[:poll_interval] || 1
|
47
|
+
@reconnect_delay = cfg[:reconnectDelay] || 5
|
48
|
+
|
49
|
+
@max_message_size = cfg[:max_message_size].to_i > 0 ? cfg[:max_message_size].to_i : 8
|
50
|
+
|
51
|
+
@aws_url = "#{@protocol}://#{@host}/"
|
52
|
+
|
53
|
+
@cache_queue_list = cfg[:cache_queue_list].nil? ? true : cfg[:cache_queue_list]
|
54
|
+
@reliable = cfg[:reliable].nil? ? true : cfg[:reliable]
|
55
|
+
|
56
|
+
#initialize the subscriptions and queues
|
57
|
+
@subscriptions = {}
|
58
|
+
@queues_by_priority = {}
|
59
|
+
@current_subscription = 0
|
60
|
+
queues
|
61
|
+
end
|
62
|
+
|
63
|
+
def disconnect
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
|
67
|
+
# queue_name string, headers hash
|
68
|
+
# for sqs, make sure queue exists, if not create, then add to list of polled queues
|
69
|
+
def subscribe queue_name, message_headers={}
|
70
|
+
# look at the existing queues, create any that are missing
|
71
|
+
queue = get_or_create_queue queue_name
|
72
|
+
if @subscriptions.has_key? queue.name
|
73
|
+
@subscriptions[queue.name].add
|
74
|
+
else
|
75
|
+
@subscriptions[queue.name] = Subscription.new(queue.name, message_headers)
|
76
|
+
end
|
77
|
+
priority = @subscriptions[queue.name].priority
|
78
|
+
|
79
|
+
@queues_by_priority[priority] = [] unless @queues_by_priority.has_key?(priority)
|
80
|
+
@queues_by_priority[priority] << queue.name unless @queues_by_priority[priority].include?(queue.name)
|
81
|
+
end
|
82
|
+
|
83
|
+
# queue_name string, headers hash
|
84
|
+
# for sqs, attempt delete the queues, won't work if not empty, that's ok
|
85
|
+
def unsubscribe queue_name, message_headers={}
|
86
|
+
if @subscriptions[queue_name]
|
87
|
+
@subscriptions[queue_name].remove
|
88
|
+
if @subscriptions[queue_name].count <= 0
|
89
|
+
sub = @subscriptions.delete(queue_name)
|
90
|
+
@queues_by_priority[sub.priority].delete(queue_name)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# queue_name string, body string, headers hash
|
96
|
+
# send a single message to a queue
|
97
|
+
def send(queue_name, message_body, message_headers = {})
|
98
|
+
queue = get_or_create_queue(queue_name)
|
99
|
+
send_messsage queue, message_body
|
100
|
+
end
|
101
|
+
|
102
|
+
# new receive respects priorities
|
103
|
+
def receive(options = {})
|
104
|
+
message = nil
|
105
|
+
|
106
|
+
only_priorities = options[:priorities]
|
107
|
+
|
108
|
+
# loop through the priorities
|
109
|
+
@queues_by_priority.keys.sort.each do |priority|
|
110
|
+
|
111
|
+
# skip this priority if there is a list, and it is not in the list
|
112
|
+
next if only_priorities && !only_priorities.include?(priority.to_i)
|
113
|
+
|
114
|
+
# loop through queues for the same priority in random order each time
|
115
|
+
@queues_by_priority[priority].shuffle.each do |queue_name|
|
116
|
+
queue = queues[queue_name]
|
117
|
+
subscription = @subscriptions[queue_name]
|
118
|
+
|
119
|
+
next if queue.nil? || subscription.nil?
|
120
|
+
messages = retrieve_messsages(queue, 1, subscription.headers[:visibility_timeout])
|
121
|
+
|
122
|
+
if (messages && !messages.empty?)
|
123
|
+
message = messages[0]
|
124
|
+
end
|
125
|
+
|
126
|
+
break if message
|
127
|
+
end
|
128
|
+
|
129
|
+
break if message
|
130
|
+
end
|
131
|
+
message
|
132
|
+
end
|
133
|
+
|
134
|
+
def received(message, headers={})
|
135
|
+
begin
|
136
|
+
delete_message(message)
|
137
|
+
rescue Object => exception
|
138
|
+
logger.error "Exception in ActiveMessaging::Adapters::AmazonSWS::Connection.received() logged and ignored: "
|
139
|
+
logger.error exception
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# do nothing; by not deleting the message will eventually become visible again
|
144
|
+
def unreceive(message, headers = {})
|
145
|
+
return true
|
146
|
+
end
|
147
|
+
|
148
|
+
def create_queue(name)
|
149
|
+
validate_new_queue name
|
150
|
+
response = make_request('CreateQueue', nil, { 'QueueName' => name }, {
|
151
|
+
'DelaySeconds' => 0,
|
152
|
+
'MaximumMessageSize' => 262144,
|
153
|
+
'MessageRetentionPeriod' => 4 * 24 * 60 * 60,
|
154
|
+
'ReceiveMessageWaitTimeSeconds' => 0,
|
155
|
+
'VisibilityTimeout' => 90 * 60
|
156
|
+
})
|
157
|
+
add_queue(response.get_text("/CreateQueueResponse/CreateQueueResult/QueueUrl")) unless response.nil?
|
158
|
+
end
|
159
|
+
|
160
|
+
def delete_queue(queue)
|
161
|
+
validate_queue queue
|
162
|
+
response = make_request('DeleteQueue', queue.queue_url)
|
163
|
+
queues.delete(queue.name)
|
164
|
+
end
|
165
|
+
|
166
|
+
def list_queues(queue_name_prefix = nil)
|
167
|
+
validate_queue_name queue_name_prefix unless queue_name_prefix.nil?
|
168
|
+
params = queue_name_prefix.nil? ? {} : { "QueueNamePrefix" => queue_name_prefix }
|
169
|
+
response = make_request('ListQueues', nil, params)
|
170
|
+
response.nil? ? [] : response.nodes("/ListQueuesResponse/ListQueuesResult/QueueUrl").collect{ |n| add_queue(n.text) }
|
171
|
+
end
|
172
|
+
|
173
|
+
def get_queue_attributes(queue, attributes = ['All'])
|
174
|
+
params = {}
|
175
|
+
attributes.each_with_index do |attribute, i|
|
176
|
+
validate_get_queue_attribute(attribute)
|
177
|
+
params["AttributeName.#{i+1}"] = attribute
|
178
|
+
end
|
179
|
+
response = make_request('GetQueueAttributes', queue.queue_url, params)
|
180
|
+
attributes = {}
|
181
|
+
response.each_node('/GetQueueAttributesResponse/GetQueueAttributesResult/Attribute') { |n|
|
182
|
+
name = n.elements['Name'].text
|
183
|
+
value = n.elements['Value'].text
|
184
|
+
attributes[name] = value
|
185
|
+
}
|
186
|
+
attributes
|
187
|
+
end
|
188
|
+
|
189
|
+
def set_queue_attributes(queue, attributes)
|
190
|
+
attributes.keys.each { |a| validate_set_queue_attribute(a) }
|
191
|
+
response = make_request('SetQueueAttributes', queue.queue_url, {}, attributes)
|
192
|
+
end
|
193
|
+
|
194
|
+
def delete_queue(queue)
|
195
|
+
validate_queue queue
|
196
|
+
response = make_request('DeleteQueue', queue.queue_url)
|
197
|
+
end
|
198
|
+
|
199
|
+
def send_messsage(queue, message)
|
200
|
+
validate_queue queue
|
201
|
+
validate_message message
|
202
|
+
response = make_request('SendMessage', queue.queue_url, { 'MessageBody' => message })
|
203
|
+
response.get_text('/SendMessageResponse/SendMessageResult/MessageId') unless response.nil?
|
204
|
+
end
|
205
|
+
|
206
|
+
def retrieve_messsages(queue, num_messages = 1, timeout = nil, waittime = nil)
|
207
|
+
validate_queue queue
|
208
|
+
validate_number_of_messages num_messages
|
209
|
+
validate_timeout timeout if timeout
|
210
|
+
|
211
|
+
params = { 'MaxNumberOfMessages' => num_messages.to_s, 'AttributeName' => 'All' }
|
212
|
+
params['VisibilityTimeout'] = timeout.to_s if timeout
|
213
|
+
params['WaitTimeSeconds'] = waittime.to_s if waittime
|
214
|
+
|
215
|
+
response = make_request('ReceiveMessage', queue.queue_url, params)
|
216
|
+
response.nodes('/ReceiveMessageResponse/ReceiveMessageResult/Message').map do |n|
|
217
|
+
Message.from_element(n, response, queue)
|
218
|
+
end unless response.nil?
|
219
|
+
end
|
220
|
+
|
221
|
+
def delete_message message
|
222
|
+
response = make_request('DeleteMessage', message.queue.queue_url, { 'ReceiptHandle' => message.receipt_handle })
|
223
|
+
end
|
224
|
+
|
225
|
+
def make_request(action, url=nil, params = {}, attributes = {})
|
226
|
+
url ||= @aws_url
|
227
|
+
|
228
|
+
params['Action'] = action
|
229
|
+
params['Version'] = @aws_version
|
230
|
+
params['Expires']= (Time.now + @request_expires).utc.iso8601
|
231
|
+
|
232
|
+
attributes.keys.sort.each_with_index do |k, i|
|
233
|
+
params["Attributes.#{i + 1}.Name"] = k
|
234
|
+
params["Attributes.#{i + 1}.Value"] = attributes[k]
|
235
|
+
end
|
236
|
+
|
237
|
+
# Sort and encode query params
|
238
|
+
query_params = params.keys.sort.map do |key|
|
239
|
+
key + "=" + ERB::Util.url_encode(params[key].to_s)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Put these together with the uri to get the request query string
|
243
|
+
request_url = "#{url}?#{query_params.join("&")}"
|
244
|
+
|
245
|
+
# Create the request
|
246
|
+
init_headers = {
|
247
|
+
"Date" => Time.now.utc.iso8601,
|
248
|
+
"Host" => @host
|
249
|
+
}
|
250
|
+
request = Net::HTTP::Get.new(request_url, init_headers)
|
251
|
+
|
252
|
+
# Sign the request
|
253
|
+
signer = AWS4Signer.new({
|
254
|
+
:access_key => @access_key_id,
|
255
|
+
:secret_key => @secret_access_key,
|
256
|
+
:region => @region
|
257
|
+
})
|
258
|
+
|
259
|
+
headers = {}
|
260
|
+
request.canonical_each { |k, v| headers[k] = v }
|
261
|
+
|
262
|
+
signature = signer.sign('GET', URI.parse(request_url), headers)
|
263
|
+
signature.each { |k, v| request[k] = v }
|
264
|
+
|
265
|
+
# Make the request
|
266
|
+
retry_count = 0
|
267
|
+
while retry_count < @request_retry_count.to_i
|
268
|
+
retry_count = retry_count + 1
|
269
|
+
begin
|
270
|
+
http_response = http_request(host,port,request)
|
271
|
+
response = SQSResponse.new(http_response)
|
272
|
+
check_errors(response)
|
273
|
+
return response
|
274
|
+
rescue Object=>ex
|
275
|
+
raise ex unless reliable
|
276
|
+
sleep(@reconnect_delay)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def http_request h, p, r
|
282
|
+
http = Net::HTTP.new(h, p)
|
283
|
+
# http.set_debug_output(STDOUT)
|
284
|
+
|
285
|
+
http.use_ssl = true if "https" == @protocol
|
286
|
+
|
287
|
+
# Don't carp about SSL cert verification
|
288
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
289
|
+
return http.request(r)
|
290
|
+
end
|
291
|
+
|
292
|
+
def check_errors(response)
|
293
|
+
raise "http response was nil" if (response.nil?)
|
294
|
+
raise response.errors if (response && response.errors?)
|
295
|
+
response
|
296
|
+
end
|
297
|
+
|
298
|
+
def queues
|
299
|
+
return @queues if (@queues && cache_queue_list)
|
300
|
+
@queues = {}
|
301
|
+
list_queues.each { |q| @queues[q.name] = q }
|
302
|
+
return @queues
|
303
|
+
end
|
304
|
+
|
305
|
+
# internal data structure methods
|
306
|
+
|
307
|
+
def add_queue(url)
|
308
|
+
q = Queue.from_url url
|
309
|
+
queues[q.name] = q if self.cache_queue_list
|
310
|
+
return q
|
311
|
+
end
|
312
|
+
|
313
|
+
def get_or_create_queue queue_name
|
314
|
+
qs = queues
|
315
|
+
q = qs.has_key?(queue_name) ? qs[queue_name] : create_queue(queue_name)
|
316
|
+
raise "could not get or create queue: #{queue_name}" unless q
|
317
|
+
q
|
318
|
+
end
|
319
|
+
|
320
|
+
# validation methods
|
321
|
+
def validate_queue_name qn
|
322
|
+
raise "Queue name, '#{qn}', must be between #{QUEUE_NAME_LENGTH.min} and #{QUEUE_NAME_LENGTH.max} characters." unless QUEUE_NAME_LENGTH.include?(qn.length)
|
323
|
+
raise "Queue name, '#{qn}', must be alphanumeric only." if (qn =~ /[^\w\-\_]/ )
|
324
|
+
end
|
325
|
+
|
326
|
+
def validate_new_queue qn
|
327
|
+
validate_queue_name qn
|
328
|
+
raise "Queue already exists: #{qn}" if queues.has_key? qn
|
329
|
+
end
|
330
|
+
|
331
|
+
def validate_queue q
|
332
|
+
raise "Never heard of queue, can't use it: #{q.name}" unless queues.has_key? q.name
|
333
|
+
end
|
334
|
+
|
335
|
+
def validate_message m
|
336
|
+
raise "Message cannot be nil." if m.nil?
|
337
|
+
raise "Message length, #{m.length}, must be between #{message_size_range.min} and #{message_size_range.max}." unless message_size_range.include?(m.length)
|
338
|
+
end
|
339
|
+
|
340
|
+
def message_size_range
|
341
|
+
@_message_size_range ||= 1..(max_message_size * 1024)
|
342
|
+
end
|
343
|
+
|
344
|
+
def validate_timeout to
|
345
|
+
raise "Timeout, #{to}, must be between #{VISIBILITY_TIMEOUT.min} and #{VISIBILITY_TIMEOUT.max}." unless VISIBILITY_TIMEOUT.include?(to)
|
346
|
+
end
|
347
|
+
|
348
|
+
def validate_get_queue_attribute qa
|
349
|
+
raise "Queue Attribute name, #{qa}, not in list of valid attributes to get: #{GET_QUEUE_ATTRIBUTES.to_sentence}." unless GET_QUEUE_ATTRIBUTES.include?(qa)
|
350
|
+
end
|
351
|
+
|
352
|
+
def validate_set_queue_attribute qa
|
353
|
+
raise "Queue Attribute name, #{qa}, not in list of valid attributes to set: #{SET_QUEUE_ATTRIBUTES.to_sentence}." unless SET_QUEUE_ATTRIBUTES.include?(qa)
|
354
|
+
end
|
355
|
+
|
356
|
+
def validate_number_of_messages nom
|
357
|
+
raise "Number of messages, #{nom}, must be between #{NUMBER_OF_MESSAGES.min} and #{NUMBER_OF_MESSAGES.max}." unless NUMBER_OF_MESSAGES.include?(nom)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
class SQSResponse
|
362
|
+
attr_accessor :headers, :doc, :http_response
|
363
|
+
|
364
|
+
def initialize response
|
365
|
+
# puts "response.body = #{response.body}"
|
366
|
+
@http_response = response
|
367
|
+
@headers = response.to_hash()
|
368
|
+
@doc = REXML::Document.new(response.body)
|
369
|
+
end
|
370
|
+
|
371
|
+
def message_type
|
372
|
+
return doc ? doc.root.name : ''
|
373
|
+
end
|
374
|
+
|
375
|
+
def errors?
|
376
|
+
(not http_response.kind_of?(Net::HTTPSuccess)) or (message_type == 'ErrorResponse')
|
377
|
+
end
|
378
|
+
|
379
|
+
def errors
|
380
|
+
return "HTTP Error: #{http_response.code} : #{http_response.message}" unless http_response.kind_of?(Net::HTTPSuccess)
|
381
|
+
|
382
|
+
msg = nil
|
383
|
+
each_node('//Error') { |n|
|
384
|
+
msg ||= ""
|
385
|
+
c = n.elements['Code'].text
|
386
|
+
m = n.elements['Message'].text
|
387
|
+
msg << ", " if msg != ""
|
388
|
+
msg << "#{c} : #{m}"
|
389
|
+
}
|
390
|
+
|
391
|
+
return msg
|
392
|
+
end
|
393
|
+
|
394
|
+
def get_text(xpath,default='')
|
395
|
+
e = REXML::XPath.first( doc, xpath)
|
396
|
+
e.nil? ? default : e.text
|
397
|
+
end
|
398
|
+
|
399
|
+
def each_node(xp)
|
400
|
+
REXML::XPath.each(doc.root, xp) {|n| yield n}
|
401
|
+
end
|
402
|
+
|
403
|
+
def nodes(xp)
|
404
|
+
doc.elements.to_a(xp)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
class Subscription
|
409
|
+
attr_accessor :destination, :headers, :count, :priority
|
410
|
+
|
411
|
+
def initialize(destination, headers={}, count=1)
|
412
|
+
@priority = headers.delete(:priority) || 1001
|
413
|
+
@destination, @headers, @count = destination, headers, count
|
414
|
+
end
|
415
|
+
|
416
|
+
def add
|
417
|
+
@count += 1
|
418
|
+
end
|
419
|
+
|
420
|
+
def remove
|
421
|
+
@count -= 1
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
class Queue
|
426
|
+
attr_accessor :url, :name, :pathinfo, :domain, :visibility_timeout
|
427
|
+
|
428
|
+
def self.from_url url
|
429
|
+
uri = URI.parse(url)
|
430
|
+
name = uri.path.split('/').last
|
431
|
+
domain = uri.host
|
432
|
+
return Queue.new(name, uri)
|
433
|
+
end
|
434
|
+
|
435
|
+
def queue_url
|
436
|
+
url.to_s
|
437
|
+
end
|
438
|
+
|
439
|
+
def initialize name, url, vt=nil
|
440
|
+
@name, @url, @visibility_timeout = name, url, vt
|
441
|
+
end
|
442
|
+
|
443
|
+
def to_s
|
444
|
+
"<AmazonSQS::Queue name='#{name}' url='#{url}' visibility_timeout='#{visibility_timeout}'>"
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# based on stomp message, has pointer to the SQSResponseObject
|
449
|
+
class Message < ActiveMessaging::BaseMessage
|
450
|
+
attr_accessor :response, :queue, :md5_of_body, :receipt_handle, :request_id, :attributes
|
451
|
+
|
452
|
+
def self.from_element(e, response, queue)
|
453
|
+
attributes = {}
|
454
|
+
e.elements.each('Attribute') { |n| attributes[n.elements['Name'].text] = n.elements['Value'].text }
|
455
|
+
|
456
|
+
Message.new(
|
457
|
+
e.elements['Body'].text,
|
458
|
+
response.headers,
|
459
|
+
e.elements['MessageId'].text,
|
460
|
+
e.elements['MD5OfBody'].text,
|
461
|
+
e.elements['ReceiptHandle'].text,
|
462
|
+
attributes,
|
463
|
+
response,
|
464
|
+
queue)
|
465
|
+
end
|
466
|
+
|
467
|
+
def initialize body, headers, id, md5_of_body, receipt_handle, attributes, response, queue
|
468
|
+
super(body, id, headers, queue.name)
|
469
|
+
@md5_of_body, @receipt_handle, @response, @queue = md5_of_body, receipt_handle, response, queue
|
470
|
+
end
|
471
|
+
|
472
|
+
def to_s
|
473
|
+
"<AmazonSQS::Message id='#{id}' body='#{body}' headers='#{headers.inspect}' attributes='#{attributes.inspect}' response='#{response}'>"
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
metadata
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: activemessaging
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 37
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 14
|
9
|
+
- 1
|
10
|
+
version: 0.14.1
|
5
11
|
platform: ruby
|
6
|
-
authors:
|
12
|
+
authors:
|
7
13
|
- Jon Tirsen
|
8
14
|
- Andrew Kuklewicz
|
9
15
|
- Olle Jonsson
|
@@ -15,105 +21,106 @@ authors:
|
|
15
21
|
autorequire:
|
16
22
|
bindir: exe
|
17
23
|
cert_chain: []
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
|
25
|
+
date: 2018-06-05 00:00:00 Z
|
26
|
+
dependencies:
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
24
31
|
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
|
27
|
-
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
hash: 3
|
34
|
+
segments:
|
35
|
+
- 0
|
36
|
+
version: "0"
|
28
37
|
prerelease: false
|
29
|
-
|
30
|
-
|
38
|
+
requirement: *id001
|
39
|
+
type: :development
|
40
|
+
name: bundler
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
31
45
|
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
|
34
|
-
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
hash: 3
|
48
|
+
segments:
|
49
|
+
- 0
|
50
|
+
version: "0"
|
51
|
+
prerelease: false
|
52
|
+
requirement: *id002
|
53
|
+
type: :development
|
35
54
|
name: test-unit
|
36
|
-
|
37
|
-
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
38
59
|
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
|
41
|
-
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
42
65
|
prerelease: false
|
43
|
-
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
- !ruby/object:Gem::Dependency
|
49
|
-
name: stomp
|
50
|
-
requirement: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
66
|
+
requirement: *id003
|
55
67
|
type: :development
|
56
|
-
prerelease: false
|
57
|
-
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
68
|
name: appraisal
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - <
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 7
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 4
|
79
|
+
version: "1.4"
|
70
80
|
prerelease: false
|
71
|
-
|
72
|
-
|
81
|
+
requirement: *id004
|
82
|
+
type: :development
|
83
|
+
name: stomp
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
73
88
|
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
|
76
|
-
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
prerelease: false
|
95
|
+
requirement: *id005
|
96
|
+
type: :runtime
|
77
97
|
name: activesupport
|
78
|
-
|
79
|
-
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
80
102
|
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
|
83
|
-
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
84
108
|
prerelease: false
|
85
|
-
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
- !ruby/object:Gem::Dependency
|
91
|
-
name: rake
|
92
|
-
requirement: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
109
|
+
requirement: *id006
|
97
110
|
type: :runtime
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
104
|
-
description: ActiveMessaging is an attempt to bring the simplicity and elegance of
|
105
|
-
rails development to the world of messaging. Messaging, (or event-driven architecture)
|
106
|
-
is widely used for enterprise integration, with frameworks such as Java's JMS, and
|
107
|
-
products such as ActiveMQ, Tibco, IBM MQSeries, etc. Now supporting Rails 3 as of
|
108
|
-
version 0.8.0.
|
109
|
-
email:
|
111
|
+
name: rake
|
112
|
+
description: ActiveMessaging is an attempt to bring the simplicity and elegance of rails development to the world of messaging. Messaging, (or event-driven architecture) is widely used for enterprise integration, with frameworks such as Java's JMS, and products such as ActiveMQ, Tibco, IBM MQSeries, etc. Now supporting Rails 3 as of version 0.8.0.
|
113
|
+
email:
|
110
114
|
- activemessaging-discuss@googlegroups.com
|
111
115
|
executables: []
|
116
|
+
|
112
117
|
extensions: []
|
118
|
+
|
113
119
|
extra_rdoc_files: []
|
114
|
-
|
115
|
-
|
116
|
-
-
|
120
|
+
|
121
|
+
files:
|
122
|
+
- .gitignore
|
123
|
+
- .travis.yml
|
117
124
|
- Appraisals
|
118
125
|
- Gemfile
|
119
126
|
- README.md
|
@@ -152,10 +159,12 @@ files:
|
|
152
159
|
- lib/activemessaging/adapter.rb
|
153
160
|
- lib/activemessaging/adapters/amqp.rb
|
154
161
|
- lib/activemessaging/adapters/asqs.rb
|
162
|
+
- lib/activemessaging/adapters/aws4_signer.rb
|
155
163
|
- lib/activemessaging/adapters/base.rb
|
156
164
|
- lib/activemessaging/adapters/beanstalk.rb
|
157
165
|
- lib/activemessaging/adapters/jms.rb
|
158
166
|
- lib/activemessaging/adapters/reliable_msg.rb
|
167
|
+
- lib/activemessaging/adapters/sqs.rb
|
159
168
|
- lib/activemessaging/adapters/stomp.rb
|
160
169
|
- lib/activemessaging/adapters/synch.rb
|
161
170
|
- lib/activemessaging/adapters/test.rb
|
@@ -186,28 +195,37 @@ files:
|
|
186
195
|
- lib/generators/active_messaging/processor/templates/processor_test.rb
|
187
196
|
- lib/tasks/start_consumers.rake
|
188
197
|
homepage: http://github.com/kookster/activemessaging
|
189
|
-
licenses:
|
198
|
+
licenses:
|
190
199
|
- MIT
|
191
|
-
metadata: {}
|
192
200
|
post_install_message:
|
193
201
|
rdoc_options: []
|
194
|
-
|
202
|
+
|
203
|
+
require_paths:
|
195
204
|
- lib
|
196
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
197
|
-
|
205
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
206
|
+
none: false
|
207
|
+
requirements:
|
198
208
|
- - ">="
|
199
|
-
- !ruby/object:Gem::Version
|
200
|
-
|
201
|
-
|
202
|
-
|
209
|
+
- !ruby/object:Gem::Version
|
210
|
+
hash: 3
|
211
|
+
segments:
|
212
|
+
- 0
|
213
|
+
version: "0"
|
214
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
215
|
+
none: false
|
216
|
+
requirements:
|
203
217
|
- - ">="
|
204
|
-
- !ruby/object:Gem::Version
|
205
|
-
|
218
|
+
- !ruby/object:Gem::Version
|
219
|
+
hash: 3
|
220
|
+
segments:
|
221
|
+
- 0
|
222
|
+
version: "0"
|
206
223
|
requirements: []
|
224
|
+
|
207
225
|
rubyforge_project:
|
208
|
-
rubygems_version:
|
226
|
+
rubygems_version: 1.8.15
|
209
227
|
signing_key:
|
210
|
-
specification_version:
|
211
|
-
summary: Official activemessaging gem, now hosted on github.com/kookster. (kookster
|
212
|
-
prefix temporary)
|
228
|
+
specification_version: 3
|
229
|
+
summary: Official activemessaging gem, now hosted on github.com/kookster. (kookster prefix temporary)
|
213
230
|
test_files: []
|
231
|
+
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA256:
|
3
|
-
metadata.gz: 32e4a6d354a60e3eb51a79ca91a496658fffe39f7e1d5191d4448fed8156f956
|
4
|
-
data.tar.gz: 2be0c55eb42ce095f81ae2167136523056c92f93bb79dd3e76ba4bc3a3ce483d
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: 0b88f4edea3a07ae4af816d587820a0aed25e597404b18a54d97cdae73107ae8c23c78e9a3c6c8ac404fde38d5130f2aa1f623a7ec0143b339c58cd619099d8a
|
7
|
-
data.tar.gz: 848bcf35b46f70a54e4bfcf36c04778038df439d080f189bffe0f582b098ba7314e2b777f906f1ed20c4e7f13bf4eea25b0b428293ae0510cb3490cbb1bfd639
|