simple_qs 0.1.1 → 0.1.2
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.
- data/CHANGELOG.md +8 -0
- data/LICENSE +20 -0
- data/README.md +250 -0
- data/TODO.md +3 -0
- data/lib/simple_qs.rb +40 -0
- data/lib/simple_qs/message.rb +135 -0
- data/lib/simple_qs/queue.rb +273 -0
- data/lib/simple_qs/request.rb +24 -0
- data/lib/simple_qs/request/base.rb +105 -0
- data/lib/simple_qs/request/get.rb +12 -0
- data/lib/simple_qs/request/post.rb +12 -0
- data/lib/simple_qs/responce.rb +30 -0
- data/lib/simple_qs/responce/exceptions.rb +38 -0
- data/lib/simple_qs/responce/failure_builder.rb +33 -0
- data/lib/simple_qs/responce/successful_builder.rb +26 -0
- data/lib/version.rb +3 -0
- metadata +32 -5
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Marjan Krekoten'
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
# English #
|
2
|
+
|
3
|
+
## Installation ##
|
4
|
+
|
5
|
+
[sudo] gem install simple_qs
|
6
|
+
|
7
|
+
## Usage ##
|
8
|
+
|
9
|
+
require 'simple_qs'
|
10
|
+
SimpleQS.account_id = 'your-account-id'
|
11
|
+
SimpleQS.access_key_id = 'YOUR_ACCESS_KEY'
|
12
|
+
SimpleQS.secret_access_key = 'YOUR_SECRET_ACCESS_KEY'
|
13
|
+
|
14
|
+
### Region ###
|
15
|
+
|
16
|
+
You can select region for queue:
|
17
|
+
|
18
|
+
# :default => 'queue.amazonaws.com'
|
19
|
+
# :us_west_1 => 'us-west-1.queue.amazonaws.com'
|
20
|
+
# :eu_west_1 => 'eu-west-1.queue.amazonaws.com'
|
21
|
+
SimpleQS.host # => 'queue.amazonaws.com'
|
22
|
+
|
23
|
+
SimpleQS.host = :eu_west_1
|
24
|
+
SimpleQS.host # => 'eu-west-1.queue.amazonaws.com'
|
25
|
+
|
26
|
+
### Queues ###
|
27
|
+
|
28
|
+
Create a queue:
|
29
|
+
|
30
|
+
queue = SimpleQS::Queue.create('queue_name')
|
31
|
+
|
32
|
+
Or:
|
33
|
+
|
34
|
+
SimpleQS::Queue.create('queue_name') do |queue|
|
35
|
+
# ...
|
36
|
+
end
|
37
|
+
|
38
|
+
Create a queue with specific default visibility timeout for received messages:
|
39
|
+
|
40
|
+
SimpleQS::Queue.create('queue_name', 60) # Visibility timeout set to 60 secinds instead of default 30
|
41
|
+
|
42
|
+
List all account's queues:
|
43
|
+
|
44
|
+
SimpleQS::Queue.list # returns list of SimpleQS::Queue objects
|
45
|
+
|
46
|
+
List all account's queues starting with 'test':
|
47
|
+
|
48
|
+
SimpleQS::Queue.list('test')
|
49
|
+
|
50
|
+
Change default visibility timeout for queue:
|
51
|
+
|
52
|
+
SimpleQS::Queue.new('queue_name').set_visibility_timeout(120)
|
53
|
+
|
54
|
+
Set queue attributes:
|
55
|
+
|
56
|
+
SimpleQS::Queue.new('queue_name').set_attributes({:VisibilityTimeout => 120, :Policy => "policy JSON here"})
|
57
|
+
|
58
|
+
Get queue attributes:
|
59
|
+
|
60
|
+
SimpleQS::Queue.new('queue_name').get_attributes(:All)
|
61
|
+
|
62
|
+
Add permissions:
|
63
|
+
|
64
|
+
SimpleQS::Queue.new('queue_name').add_permissions('testPerms', [
|
65
|
+
{:account_id => '098166147350', :action => 'SendMessage'},
|
66
|
+
{:account_id => '098166147350', :action => 'ReceiveMessage'}
|
67
|
+
])
|
68
|
+
|
69
|
+
Remove permissions:
|
70
|
+
|
71
|
+
SimpleQS::Queue.new('queue_name').remove_permissions('testPerms')
|
72
|
+
|
73
|
+
Delete queue:
|
74
|
+
|
75
|
+
SimpleQS::Queue.new('queue_name').delete
|
76
|
+
|
77
|
+
### Messages ###
|
78
|
+
|
79
|
+
Send message:
|
80
|
+
|
81
|
+
queue = SimpleQS::Queue.new('queue_name')
|
82
|
+
queue.send_message('message 1')
|
83
|
+
queue.send_message('message 2')
|
84
|
+
|
85
|
+
Or:
|
86
|
+
|
87
|
+
SimpleQS::Message.send(queue, 'message 1')
|
88
|
+
SimpleQS::Message.send(queue, 'message 2')
|
89
|
+
|
90
|
+
Receive message(s):
|
91
|
+
|
92
|
+
queue.receive_messages # always returns array of messages
|
93
|
+
queue.receive_messages(:All, 10, 70) # receive maximum 10 messages with all attributes (:All)
|
94
|
+
# and set for them visibility timeout to 70 seconds
|
95
|
+
|
96
|
+
Or:
|
97
|
+
|
98
|
+
SimpleQS::Message.receive(queue)
|
99
|
+
SimpleQS::Message.receive(queue, :All, 10, 70)
|
100
|
+
|
101
|
+
Resend received message:
|
102
|
+
|
103
|
+
messages = queue.receive_messages
|
104
|
+
messages.first.resend
|
105
|
+
|
106
|
+
Change visibility timeout for this message:
|
107
|
+
|
108
|
+
messages.first.change_visibility(120) # Change to 120 seconds
|
109
|
+
|
110
|
+
Delete received message:
|
111
|
+
|
112
|
+
messages.first.delete
|
113
|
+
|
114
|
+
## TODO ##
|
115
|
+
|
116
|
+
See [TODO](http://github.com/krekoten/SimpleQS/blob/master/TODO.md) file
|
117
|
+
|
118
|
+
## Issues ##
|
119
|
+
|
120
|
+
[ISSUES](http://github.com/krekoten/SimpleQS/issues)
|
121
|
+
|
122
|
+
## Authors ##
|
123
|
+
|
124
|
+
Marjan Krekoten' (krekoten@gmail.com)
|
125
|
+
|
126
|
+
# Українською #
|
127
|
+
|
128
|
+
## Встановлення ##
|
129
|
+
|
130
|
+
[sudo] gem install simple_qs
|
131
|
+
|
132
|
+
## Користування ##
|
133
|
+
|
134
|
+
require 'simple_qs'
|
135
|
+
SimpleQS.account_id = 'your-account-id'
|
136
|
+
SimpleQS.access_key_id = 'YOUR_ACCESS_KEY'
|
137
|
+
|
138
|
+
SimpleQS.secret_access_key = 'YOUR_SECRET_ACCESS_KEY'
|
139
|
+
|
140
|
+
### Регіон ###
|
141
|
+
|
142
|
+
Можна обрати регіон, в якому буде створена черга:
|
143
|
+
|
144
|
+
# :default => 'queue.amazonaws.com'
|
145
|
+
# :us_west_1 => 'us-west-1.queue.amazonaws.com'
|
146
|
+
# :eu_west_1 => 'eu-west-1.queue.amazonaws.com'
|
147
|
+
SimpleQS.host # => 'queue.amazonaws.com'
|
148
|
+
|
149
|
+
SimpleQS.host = :eu_west_1
|
150
|
+
SimpleQS.host # => 'eu-west-1.queue.amazonaws.com'
|
151
|
+
|
152
|
+
### Черги ###
|
153
|
+
|
154
|
+
Створити чергу:
|
155
|
+
|
156
|
+
queue = SimpleQS::Queue.create('queue_name')
|
157
|
+
|
158
|
+
Або:
|
159
|
+
|
160
|
+
SimpleQS::Queue.create('queue_name') do |queue|
|
161
|
+
# ...
|
162
|
+
end
|
163
|
+
|
164
|
+
Створити чергу із специфічним обмеженням часу невидимості для отриманих повідомлень:
|
165
|
+
|
166
|
+
SimpleQS::Queue.create('queue_name', 60) # Час невидимості встановленно на 60 секунд, натомість типових 30-ти
|
167
|
+
|
168
|
+
Перелічити всі черги даного рахунку:
|
169
|
+
|
170
|
+
SimpleQS::Queue.list # returns list of SimpleQS::Queue objects
|
171
|
+
|
172
|
+
Перелічити всі черги даного рахунку, що починаються на 'test':
|
173
|
+
|
174
|
+
SimpleQS::Queue.list('test')
|
175
|
+
|
176
|
+
Змінити типовий час невидимості для даної черги:
|
177
|
+
|
178
|
+
SimpleQS::Queue.new('queue_name').set_visibility_timeout(120)
|
179
|
+
|
180
|
+
Встановити атрибути черги:
|
181
|
+
|
182
|
+
SimpleQS::Queue.new('queue_name').set_attributes({:VisibilityTimeout => 120, :Policy => "policy JSON here"})
|
183
|
+
|
184
|
+
Отримати поточні атрибути черги:
|
185
|
+
|
186
|
+
SimpleQS::Queue.new('queue_name').get_attributes(:All)
|
187
|
+
|
188
|
+
Надати дозволи:
|
189
|
+
|
190
|
+
SimpleQS::Queue.new('queue_name').add_permissions('testPerms', [
|
191
|
+
{:account_id => '098166147350', :action => 'SendMessage'},
|
192
|
+
{:account_id => '098166147350', :action => 'ReceiveMessage'}
|
193
|
+
])
|
194
|
+
|
195
|
+
Скасувати дозволи:
|
196
|
+
|
197
|
+
SimpleQS::Queue.new('queue_name').remove_permissions('testPerms')
|
198
|
+
|
199
|
+
Видалити чергу:
|
200
|
+
|
201
|
+
SimpleQS::Queue.new('queue_name').delete
|
202
|
+
|
203
|
+
### Повідомлення ###
|
204
|
+
|
205
|
+
Відправити повідомлення:
|
206
|
+
|
207
|
+
queue = SimpleQS::Queue.new('queue_name')
|
208
|
+
queue.send_message('message 1')
|
209
|
+
queue.send_message('message 2')
|
210
|
+
|
211
|
+
Або:
|
212
|
+
|
213
|
+
SimpleQS::Message.send(queue, 'message 1')
|
214
|
+
SimpleQS::Message.send(queue, 'message 2')
|
215
|
+
|
216
|
+
Отримати повідомлення:
|
217
|
+
|
218
|
+
queue.receive_messages # завжди повертає масив
|
219
|
+
queue.receive_messages(:All, 10, 70) # отримати максимум 10 повідомлень зі всіма атрибутами (:All)
|
220
|
+
# та встановленим часом невидимості у 70 секунд
|
221
|
+
|
222
|
+
Або:
|
223
|
+
|
224
|
+
SimpleQS::Message.receive(queue)
|
225
|
+
SimpleQS::Message.receive(queue, :All, 10, 70)
|
226
|
+
|
227
|
+
Надіслати отримані повідомлення ще раз:
|
228
|
+
|
229
|
+
messages = queue.receive_messages
|
230
|
+
messages.first.resend
|
231
|
+
|
232
|
+
Змінити час невидимості для даного повідомлення:
|
233
|
+
|
234
|
+
messages.first.change_visibility(120) # Встановити у 120 секунд
|
235
|
+
|
236
|
+
Видалити отримане повідомлення з черги:
|
237
|
+
|
238
|
+
messages.first.delete
|
239
|
+
|
240
|
+
## Що потрібно зробити ##
|
241
|
+
|
242
|
+
Дивіться [TODO](http://github.com/krekoten/SimpleQS/blob/master/TODO.md)
|
243
|
+
|
244
|
+
## Помилки, побажання і т.і. ##
|
245
|
+
|
246
|
+
[ISSUES](http://github.com/krekoten/SimpleQS/issues)
|
247
|
+
|
248
|
+
## Автори ##
|
249
|
+
|
250
|
+
Мар'ян Крекотень (krekoten@gmail.com)
|
data/TODO.md
ADDED
data/lib/simple_qs.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
$: << File.join(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
module SimpleQS
|
6
|
+
|
7
|
+
API_VERSION = '2009-02-01'
|
8
|
+
|
9
|
+
SQS_HOSTS = {
|
10
|
+
:default => 'queue.amazonaws.com',
|
11
|
+
:us_west_1 => 'us-west-1.queue.amazonaws.com',
|
12
|
+
:eu_west_1 => 'eu-west-1.queue.amazonaws.com'
|
13
|
+
}
|
14
|
+
|
15
|
+
autoload :Message, 'simple_qs/message'
|
16
|
+
autoload :Queue, 'simple_qs/queue'
|
17
|
+
autoload :Request, 'simple_qs/request'
|
18
|
+
autoload :Responce, 'simple_qs/responce'
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_accessor :access_key_id, :secret_access_key
|
22
|
+
|
23
|
+
def account_id=(value)
|
24
|
+
@account_id = value.gsub(/[^0-9]/, '')
|
25
|
+
end
|
26
|
+
attr_reader :account_id
|
27
|
+
|
28
|
+
def host=(value)
|
29
|
+
raise ArgumentError, 'Expected value to be one of: :default, :us_west_1, :eu_west_1' unless SQS_HOSTS.key?(value)
|
30
|
+
@host = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def host
|
34
|
+
@host ||= :default
|
35
|
+
SQS_HOSTS[@host]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'version'
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module SimpleQS
|
2
|
+
class Message
|
3
|
+
|
4
|
+
class DoubleSendError < StandardError; end
|
5
|
+
class NotReceivedError < StandardError; end
|
6
|
+
|
7
|
+
attr_accessor :queue
|
8
|
+
attr_reader :message_id, :receipt_handle, :body, :md5_of_body
|
9
|
+
attr_reader :sender_id, :sent_timestamp, :approximate_receive_count, :approximate_first_receive_timestamp
|
10
|
+
|
11
|
+
def initialize(queue, params = nil)
|
12
|
+
@queue = queue
|
13
|
+
|
14
|
+
@body = params if params.class == String
|
15
|
+
_from_responce(params) if params.class == SimpleQS::Responce
|
16
|
+
_from_hash(params) if params.class == Hash
|
17
|
+
end
|
18
|
+
|
19
|
+
def send
|
20
|
+
raise DoubleSendError, "Cann't send already sent message. Use resend() method." if message_id
|
21
|
+
raise DoubleSendError, "Cann't send received message. Use resend() method." if receipt_handle
|
22
|
+
|
23
|
+
params = {
|
24
|
+
'Action' => 'SendMessage',
|
25
|
+
'MessageBody' => body
|
26
|
+
}
|
27
|
+
request = queue.build_request(:post, params)
|
28
|
+
responce = request.perform
|
29
|
+
raise responce.to_error unless responce.successful?
|
30
|
+
_from_responce(responce)
|
31
|
+
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def resend
|
36
|
+
dup.send
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete
|
40
|
+
raise NotReceivedError, "Cann't delete message that was not received" unless receipt_handle
|
41
|
+
params = {
|
42
|
+
'Action' => 'DeleteMessage',
|
43
|
+
'ReceiptHandle' => receipt_handle
|
44
|
+
}
|
45
|
+
request = queue.build_request(:get, params)
|
46
|
+
responce = request.perform
|
47
|
+
raise responce.to_error unless responce.successful?
|
48
|
+
end
|
49
|
+
alias_method :destroy, :delete
|
50
|
+
|
51
|
+
def change_visibility(visibility_timeout)
|
52
|
+
SimpleQS::Queue.check_visibility_timeout(visibility_timeout)
|
53
|
+
raise NotReceivedError, "Cann't change visibility timeout for message that was not received" unless receipt_handle
|
54
|
+
|
55
|
+
params = {
|
56
|
+
'Action' => 'ChangeMessageVisibility',
|
57
|
+
'ReceiptHandle' => receipt_handle,
|
58
|
+
'VisibilityTimeout' => visibility_timeout
|
59
|
+
}
|
60
|
+
|
61
|
+
request = queue.build_request(:get, params)
|
62
|
+
responce = request.perform
|
63
|
+
|
64
|
+
raise responce.to_error unless responce.successful?
|
65
|
+
end
|
66
|
+
|
67
|
+
def dup
|
68
|
+
self.class.new(queue, body)
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(other)
|
72
|
+
message_id == other.message_id && receipt_handle == other.receipt_handle
|
73
|
+
end
|
74
|
+
|
75
|
+
class << self
|
76
|
+
def send(queue, message_body)
|
77
|
+
new(queue, message_body).send
|
78
|
+
end
|
79
|
+
|
80
|
+
def receive(queue, attributes = nil, max_number_of_messages = nil, visibility_timeout = nil)
|
81
|
+
|
82
|
+
SimpleQS::Queue.check_visibility_timeout(visibility_timeout) if visibility_timeout
|
83
|
+
if max_number_of_messages && !(1..10).include?(max_number_of_messages)
|
84
|
+
raise ArgumentError, "Maximum number of messages should be in 1..10 range"
|
85
|
+
end
|
86
|
+
|
87
|
+
params = {
|
88
|
+
'Action' => 'ReceiveMessage'
|
89
|
+
}
|
90
|
+
|
91
|
+
if attributes
|
92
|
+
attributes = [attributes] unless attributes.class == Array
|
93
|
+
attributes.uniq!
|
94
|
+
unless (attributes - [:All, :SenderId, :SentTimestamp, :ApproximateReceiveCount, :ApproximateFirstReceiveTimestamp]).empty?
|
95
|
+
raise ArgumentError,\
|
96
|
+
"Allowed attributes: :All, :SenderId, :SentTimestamp, :ApproximateReceiveCount, :ApproximateFirstReceiveTimestamp"
|
97
|
+
end
|
98
|
+
attributes.each_index do |i|
|
99
|
+
params["AttributeName.#{i + 1}"] = attributes[i]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
params['MaxNumberOfMessages'] = max_number_of_messages if max_number_of_messages
|
103
|
+
params['VisibilityTimeout'] = visibility_timeout if visibility_timeout
|
104
|
+
|
105
|
+
request = queue.build_request(:get, params)
|
106
|
+
responce = request.perform
|
107
|
+
if responce.respond_to?(:message)
|
108
|
+
messages = (responce.message.class == Array ? responce.message : [responce.message])
|
109
|
+
messages.map {|message| new(queue, message)}
|
110
|
+
else
|
111
|
+
[]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
protected
|
117
|
+
|
118
|
+
def _from_responce(responce)
|
119
|
+
@message_id = responce.message_id
|
120
|
+
@md5_of_body = responce.md5_of_message_body
|
121
|
+
end
|
122
|
+
|
123
|
+
def _from_hash(message)
|
124
|
+
attributes = message.delete('Attribute')
|
125
|
+
attributes.each do |attr|
|
126
|
+
message[attr['Name']] = attr['Value']
|
127
|
+
end if attributes
|
128
|
+
message.each do |key, value|
|
129
|
+
key = key.gsub(/([A-Z]+)/, '_\1').downcase.gsub(/^_/, '')
|
130
|
+
instance_variable_set("@#{key}".to_sym, value) if respond_to?(key.to_sym)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
module SimpleQS
|
2
|
+
class Queue
|
3
|
+
|
4
|
+
class MaxVisibilityError < StandardError; end
|
5
|
+
|
6
|
+
MAX_VISIBILITY_TIMEOUT = 43200
|
7
|
+
|
8
|
+
attr_accessor :queue_url
|
9
|
+
|
10
|
+
# Initializes new SimpleQS::Queue object
|
11
|
+
# Parameters:
|
12
|
+
# queue_url_or_name - String, either queue url or queue name
|
13
|
+
def initialize(queue_url_or_name)
|
14
|
+
begin
|
15
|
+
self.class.check_queue_name(queue_url_or_name)
|
16
|
+
@name = queue_url_or_name
|
17
|
+
@queue_url = "http://#{SimpleQS.host}/#{SimpleQS.account_id}/#{@name}"
|
18
|
+
rescue ArgumentError
|
19
|
+
@queue_url = queue_url_or_name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get queue name
|
24
|
+
# Returns:
|
25
|
+
# String - queue name
|
26
|
+
def name
|
27
|
+
@name ||= @queue_url.split(/\//).last
|
28
|
+
end
|
29
|
+
|
30
|
+
# Is this queue deleted?
|
31
|
+
# Returns:
|
32
|
+
# Bool - true if queue is deleted
|
33
|
+
def deleted
|
34
|
+
@deleted ||= false
|
35
|
+
end
|
36
|
+
alias_method :deleted?, :deleted
|
37
|
+
|
38
|
+
|
39
|
+
# Deletes this queue
|
40
|
+
# Returns:
|
41
|
+
# Bool - true, if successful
|
42
|
+
# Raises:
|
43
|
+
# SimpleQS::Responce::Error
|
44
|
+
def delete
|
45
|
+
params = {
|
46
|
+
'Action' => 'DeleteQueue'
|
47
|
+
}
|
48
|
+
|
49
|
+
request = build_request(:get, params)
|
50
|
+
responce = request.perform
|
51
|
+
|
52
|
+
raise responce.to_error unless responce.successful?
|
53
|
+
|
54
|
+
@deleted = true
|
55
|
+
end
|
56
|
+
alias_method :destroy, :delete
|
57
|
+
|
58
|
+
def send_message(body)
|
59
|
+
SimpleQS::Message.send(self, body)
|
60
|
+
end
|
61
|
+
|
62
|
+
def receive_messages(attributes = nil, max_number_of_messages = nil, visibility_timeout = nil)
|
63
|
+
SimpleQS::Message.receive(self, attributes, max_number_of_messages, visibility_timeout)
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_attributes(attribute_or_array = nil)
|
67
|
+
params = {
|
68
|
+
'Action' => 'GetQueueAttributes'
|
69
|
+
}
|
70
|
+
|
71
|
+
if attribute_or_array
|
72
|
+
attribute_or_array = [attribute_or_array] unless attribute_or_array.class == Array
|
73
|
+
attribute_or_array.uniq!
|
74
|
+
unless (attribute_or_array - [:All, :ApproximateNumberOfMessages, :ApproximateNumberOfMessagesNotVisible,
|
75
|
+
:VisibilityTimeout, :CreatedTimestamp, :LastModifiedTimestamp, :Policy]).empty?
|
76
|
+
raise ArgumentError,\
|
77
|
+
"Allowed attributes: :All, :ApproximateNumberOfMessages, :ApproximateNumberOfMessagesNotVisible, " <<
|
78
|
+
":VisibilityTimeout, :CreatedTimestamp, :LastModifiedTimestamp, :Policy"
|
79
|
+
end
|
80
|
+
attribute_or_array.each_index do |i|
|
81
|
+
params["AttributeName.#{i + 1}"] = attribute_or_array[i]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
request = build_request(:get, params)
|
86
|
+
responce = request.perform
|
87
|
+
raise responce.to_error unless responce.successful?
|
88
|
+
|
89
|
+
if responce.respond_to?(:attribute)
|
90
|
+
responce.attribute.inject({}) do |result, key_value|
|
91
|
+
result[key_value['Name']] = key_value['Value']
|
92
|
+
result
|
93
|
+
end
|
94
|
+
else
|
95
|
+
{}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def set_visibility_timeout(visibility_timeout)
|
100
|
+
set_attributes({:VisibilityTimeout => visibility_timeout})
|
101
|
+
end
|
102
|
+
|
103
|
+
def set_policy(policy)
|
104
|
+
set_attributes({:Policy => policy})
|
105
|
+
end
|
106
|
+
|
107
|
+
def set_attributes(attributes)
|
108
|
+
raise ArgumentError, "Allowed attributes: :VisibilityTimeout, :Policy" unless (attributes.keys - [:VisibilityTimeout, :Policy]).empty?
|
109
|
+
self.class.check_visibility_timeout(attributes[:VisibilityTimeout]) if attributes[:VisibilityTimeout]
|
110
|
+
|
111
|
+
params = {
|
112
|
+
'Action' => 'SetQueueAttributes'
|
113
|
+
}
|
114
|
+
|
115
|
+
i = 1
|
116
|
+
attributes.each do |key, value|
|
117
|
+
params["Attribute.#{i}.Name"] = key.to_s
|
118
|
+
params["Attribute.#{i}.Value"] = value
|
119
|
+
i += 1
|
120
|
+
end
|
121
|
+
|
122
|
+
request = build_request(:get, params)
|
123
|
+
responce = request.perform
|
124
|
+
|
125
|
+
raise responce.to_error unless responce.successful?
|
126
|
+
end
|
127
|
+
|
128
|
+
# label - String, identifier for permissions
|
129
|
+
# permissions - Array of Hashes, [{:account_id => '125074342641', :action => 'SendMessage'}, ...]
|
130
|
+
def add_permissions(label, permissions)
|
131
|
+
self.class.check_queue_name(label)
|
132
|
+
|
133
|
+
params = {
|
134
|
+
'Action' => 'AddPermission',
|
135
|
+
'Label' => label
|
136
|
+
}
|
137
|
+
|
138
|
+
i = 1
|
139
|
+
permissions.each do |permission_hash|
|
140
|
+
raise ArgumentError, "invalid action: #{permission_hash[:action]}"\
|
141
|
+
unless ['*', 'SendMessage', 'ReceiveMessage', 'DeleteMessage',
|
142
|
+
'ChangeMessageVisibility', 'GetQueueAttributes'].include?(permission_hash[:action])
|
143
|
+
|
144
|
+
params["AWSAccountId.#{i}"] = permission_hash[:account_id]
|
145
|
+
params["ActionName.#{i}"] = permission_hash[:action]
|
146
|
+
i += 1
|
147
|
+
end
|
148
|
+
|
149
|
+
request = build_request(:post, params)
|
150
|
+
responce = request.perform
|
151
|
+
|
152
|
+
raise responce.to_error unless responce.successful?
|
153
|
+
end
|
154
|
+
|
155
|
+
def remove_permissions(label)
|
156
|
+
params = {
|
157
|
+
'Action' => 'RemovePermission',
|
158
|
+
'Label' => label
|
159
|
+
}
|
160
|
+
|
161
|
+
request = build_request(:get, params)
|
162
|
+
responce = request.perform
|
163
|
+
|
164
|
+
raise responce.to_error unless responce.successful?
|
165
|
+
end
|
166
|
+
|
167
|
+
def build_request(method, params)
|
168
|
+
request = SimpleQS::Request.build(method, params)
|
169
|
+
request.query_string = [SimpleQS.account_id, name]
|
170
|
+
request
|
171
|
+
end
|
172
|
+
|
173
|
+
class << self
|
174
|
+
# Create new queue
|
175
|
+
# Parameters:
|
176
|
+
# name - String, name of queue. Should be not longer than 80 chars and contain only a-zA-Z0-9\-\_
|
177
|
+
# default_visibility_timeout - Fixnum, visibility timeout [Optional]. In range 0 to MAX_VISIBILITY_TIMEOUT.
|
178
|
+
# If nil, then default is used (30 seconds).
|
179
|
+
# &block - if given, then newly created queue object passed to it
|
180
|
+
# Returns:
|
181
|
+
# SimpleQS::Queue
|
182
|
+
# Raises:
|
183
|
+
# ArgumentError
|
184
|
+
# SimpleQS::Queue::MaxVisibilityError
|
185
|
+
# SimpleQS::Responce::Error
|
186
|
+
def create(name, default_visibility_timeout = nil, &block)
|
187
|
+
|
188
|
+
check_queue_name(name)
|
189
|
+
check_visibility_timeout(default_visibility_timeout)
|
190
|
+
|
191
|
+
params = {
|
192
|
+
'Action' => 'CreateQueue',
|
193
|
+
'QueueName' => name
|
194
|
+
}
|
195
|
+
params['DefaultVisibilityTimeout'] = default_visibility_timeout if default_visibility_timeout
|
196
|
+
|
197
|
+
request = SimpleQS::Request.build(:get, params)
|
198
|
+
responce = request.perform
|
199
|
+
if responce.successful?
|
200
|
+
queue = new(responce.queue_url)
|
201
|
+
yield queue if block_given?
|
202
|
+
queue
|
203
|
+
else
|
204
|
+
raise responce.to_error
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# List queues
|
209
|
+
# Parameters:
|
210
|
+
# pattern - String, first letterns of queues names [Optional].
|
211
|
+
# Returns:
|
212
|
+
# Array - array of SimpleQS::Queue or empty if nothing found
|
213
|
+
# Rises:
|
214
|
+
# ArgumentError
|
215
|
+
# SimpleQS::Responce::Error
|
216
|
+
def list(pattern = nil)
|
217
|
+
params = {
|
218
|
+
'Action' => 'ListQueues'
|
219
|
+
}
|
220
|
+
|
221
|
+
check_queue_name(pattern) if pattern
|
222
|
+
params['QueueNamePrefix'] = pattern if pattern
|
223
|
+
|
224
|
+
request = SimpleQS::Request.build(:get, params)
|
225
|
+
responce = request.perform
|
226
|
+
raise responce.to_error unless responce.successful?
|
227
|
+
|
228
|
+
begin
|
229
|
+
queues = responce.queue_url.class == Array ? responce.queue_url : [responce.queue_url]
|
230
|
+
queues.map {|queue_url| new(queue_url)}
|
231
|
+
rescue NoMethodError
|
232
|
+
[]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def exists?(queue_name)
|
237
|
+
list(queue_name).any? {|queue| queue.name == queue_name}
|
238
|
+
end
|
239
|
+
|
240
|
+
def delete(queue_name)
|
241
|
+
new(queue_name).delete
|
242
|
+
end
|
243
|
+
alias_method :destroy, :delete
|
244
|
+
|
245
|
+
# Performs checks on queue name. Raises ArgumentError with message in case of
|
246
|
+
# constraint violation
|
247
|
+
def check_queue_name(name)
|
248
|
+
raise ArgumentError, "expected to be String, but got #{name.class.to_s}" unless name.class == String
|
249
|
+
raise(
|
250
|
+
ArgumentError,
|
251
|
+
"should be maximum 80 characters long"
|
252
|
+
) if name.length > 80
|
253
|
+
raise(
|
254
|
+
ArgumentError,
|
255
|
+
"should contain only alphanumeric characters, hyphens (-), and underscores (_)"
|
256
|
+
) if name =~ /[^a-zA-Z0-9\_\-]/
|
257
|
+
end
|
258
|
+
|
259
|
+
# Performs checks on visibility timeout. Raises ArgumentError or SimpleQS::Queue::MaxVisibilityError
|
260
|
+
# with message in case of constraint violation
|
261
|
+
def check_visibility_timeout(default_visibility_timeout)
|
262
|
+
raise(
|
263
|
+
ArgumentError,
|
264
|
+
"expected visibility timeout to respon to to_i, but #{default_visibility_timeout.class.to_s} doesn't"
|
265
|
+
) if default_visibility_timeout && default_visibility_timeout.class != Fixnum
|
266
|
+
raise(
|
267
|
+
MaxVisibilityError,
|
268
|
+
"expected visibility timeout to be in 0...#{MAX_VISIBILITY_TIMEOUT} range, got #{default_visibility_timeout}"
|
269
|
+
) if default_visibility_timeout && !(0...MAX_VISIBILITY_TIMEOUT).include?(default_visibility_timeout)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SimpleQS
|
2
|
+
module Request
|
3
|
+
autoload :Base, 'simple_qs/request/base'
|
4
|
+
autoload :Get, 'simple_qs/request/get'
|
5
|
+
autoload :Post, 'simple_qs/request/post'
|
6
|
+
|
7
|
+
class UnknownHttpMethod < StandardError; end
|
8
|
+
|
9
|
+
HTTP_METHODS = {
|
10
|
+
:get => :Get,
|
11
|
+
:post => :Post
|
12
|
+
}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def build(request, params = {})
|
16
|
+
if SimpleQS::Request::HTTP_METHODS.keys.include?(request)
|
17
|
+
SimpleQS::Request.const_get(HTTP_METHODS[request]).new(params)
|
18
|
+
else
|
19
|
+
raise SimpleQS::Request::UnknownHttpMethod, "Method #{request} is unknown or unsupported"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'base64'
|
4
|
+
require 'hmac-sha1'
|
5
|
+
|
6
|
+
module SimpleQS
|
7
|
+
module Request
|
8
|
+
class Base
|
9
|
+
|
10
|
+
RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~]/
|
11
|
+
SIGNATURE_VERSION = 2
|
12
|
+
SIGNATURE_METHOD = 'HmacSHA1'
|
13
|
+
|
14
|
+
def initialize(params = {})
|
15
|
+
self.query_params = params
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
self.class.http_method == other.class.http_method\
|
20
|
+
&& query_params == other.query_params\
|
21
|
+
&& query_string == other.query_string
|
22
|
+
end
|
23
|
+
|
24
|
+
def timestamp
|
25
|
+
@timestamp ||= _timestamp
|
26
|
+
end
|
27
|
+
|
28
|
+
def timestamp=(time)
|
29
|
+
raise ArgumentError, "expected Time object, bug got #{time.class.to_s} instead." unless time.kind_of?(Time)
|
30
|
+
@timestamp = time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
31
|
+
end
|
32
|
+
|
33
|
+
def query_params
|
34
|
+
@query_params ||= {}
|
35
|
+
@query_params = {
|
36
|
+
'SignatureVersion' => SIGNATURE_VERSION,
|
37
|
+
'SignatureMethod' => SIGNATURE_METHOD,
|
38
|
+
'Version' => SimpleQS::API_VERSION,
|
39
|
+
'Timestamp' => timestamp,
|
40
|
+
'AWSAccessKeyId' => SimpleQS.access_key_id
|
41
|
+
}.merge(@query_params)
|
42
|
+
@query_params.delete('Timestamp') if @query_params['Expires']
|
43
|
+
|
44
|
+
@query_params
|
45
|
+
end
|
46
|
+
attr_writer :query_params
|
47
|
+
|
48
|
+
def update_query_params(params)
|
49
|
+
@query_params = query_params.merge params
|
50
|
+
end
|
51
|
+
|
52
|
+
def query_string
|
53
|
+
@query_string ||= "/"
|
54
|
+
end
|
55
|
+
|
56
|
+
def query_string=(value)
|
57
|
+
value = value.join('/') if value.kind_of?(Array)
|
58
|
+
@query_string = (value =~ /^\// ? value : "/#{value}")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Canonicalizes query string
|
62
|
+
def canonical_query_string
|
63
|
+
params_to_query(query_params.sort)
|
64
|
+
end
|
65
|
+
|
66
|
+
def signature_base_string
|
67
|
+
[
|
68
|
+
self.class.http_method.to_s.upcase,
|
69
|
+
SimpleQS.host.downcase,
|
70
|
+
query_string,
|
71
|
+
canonical_query_string
|
72
|
+
].join("\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
def uri(with_query_params = false)
|
76
|
+
"http://#{SimpleQS.host}#{query_string}" << (with_query_params ? "?#{params_to_query(query_params)}" : '')
|
77
|
+
end
|
78
|
+
|
79
|
+
def sign!
|
80
|
+
update_query_params({
|
81
|
+
'Signature' => Base64.encode64(HMAC::SHA1.digest(SimpleQS.secret_access_key, signature_base_string)).chomp
|
82
|
+
})
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def params_to_query(params)
|
87
|
+
params.map {|pair| pair.map {|value| URI.escape(value.to_s, RESERVED_CHARACTERS)}.join('=')}.join('&')
|
88
|
+
end
|
89
|
+
|
90
|
+
class << self
|
91
|
+
def http_method(value = nil)
|
92
|
+
@http_method = value if value
|
93
|
+
@http_method
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
# Generates UTC timestamp in dateTime object format
|
100
|
+
def _timestamp
|
101
|
+
Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'xmlsimple'
|
2
|
+
|
3
|
+
require 'simple_qs/responce/exceptions'
|
4
|
+
|
5
|
+
module SimpleQS
|
6
|
+
class Responce
|
7
|
+
|
8
|
+
autoload :SuccessfulBuilder, 'simple_qs/responce/successful_builder'
|
9
|
+
autoload :FailureBuilder, 'simple_qs/responce/failure_builder'
|
10
|
+
|
11
|
+
def initialize(xml)
|
12
|
+
_parse xml
|
13
|
+
end
|
14
|
+
|
15
|
+
def successful?
|
16
|
+
!@xml_data.key?('ErrorResponse')
|
17
|
+
end
|
18
|
+
|
19
|
+
def root_element
|
20
|
+
@xml_data.keys[0]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def _parse(xml)
|
26
|
+
@xml_data = XmlSimple.xml_in(xml, {'ForceArray' => false, 'KeepRoot' => true})
|
27
|
+
(successful? ? SuccessfulBuilder : FailureBuilder).build(self)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SimpleQS
|
2
|
+
class Responce
|
3
|
+
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class AccessDeniedError < Error; end
|
7
|
+
class AuthFailureError < Error; end
|
8
|
+
class AWSSimpleQueueServiceInternalError < Error; end
|
9
|
+
class AWSSimpleQueueServiceNonExistentQueueError < Error; end
|
10
|
+
class AWSSimpleQueueServiceQueueDeletedRecently < Error; end
|
11
|
+
class AWSSimpleQueueServiceQueueNameExists < Error; end
|
12
|
+
class ConflictingQueryParameterError < Error; end
|
13
|
+
class InvalidParameterValueError < Error; end
|
14
|
+
class InternalError < Error; end
|
15
|
+
class InvalidAccessKeyIdError < Error; end
|
16
|
+
class InvalidActionError < Error; end
|
17
|
+
class InvalidAddressError < Error; end
|
18
|
+
class InvalidHttpRequestError < Error; end
|
19
|
+
class InvalidParameterCombinationError < Error; end
|
20
|
+
class InvalidParameterValueError < Error; end
|
21
|
+
class InvalidQueryParameterError < Error; end
|
22
|
+
class InvalidRequestError < Error; end
|
23
|
+
class InvalidSecurityError < Error; end
|
24
|
+
class InvalidSecurityTokenError < Error; end
|
25
|
+
class MalformedVersionError < Error; end
|
26
|
+
class MissingClientTokenIdError < Error; end
|
27
|
+
class MissingCredentialsError < Error; end
|
28
|
+
class MissingParameterError < Error; end
|
29
|
+
class NoSuchVersionError < Error; end
|
30
|
+
class NotAuthorizedToUseVersionError < Error; end
|
31
|
+
class OptInRequiredError < Error; end
|
32
|
+
class RequestExpiredError < Error; end
|
33
|
+
class RequestThrottledError < Error; end
|
34
|
+
class ServiceUnavailableError < Error; end
|
35
|
+
class SignatureDoesNotMatchError < Error; end
|
36
|
+
class X509ParseError < Error; end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SimpleQS
|
2
|
+
class Responce
|
3
|
+
class FailureBuilder
|
4
|
+
def self.build responce
|
5
|
+
responce.instance_eval do
|
6
|
+
def sender_error?
|
7
|
+
@xml_data[root_element]['Error']['Type'] == 'Sender'
|
8
|
+
end
|
9
|
+
|
10
|
+
def receiver_error?
|
11
|
+
@xml_data[root_element]['Error']['Type'] == 'Receiver'
|
12
|
+
end
|
13
|
+
|
14
|
+
def request_id
|
15
|
+
@xml_data[root_element]['RequestId']
|
16
|
+
end
|
17
|
+
|
18
|
+
def error_code
|
19
|
+
@xml_data[root_element]['Error']['Code'].gsub(/\./, '')
|
20
|
+
end
|
21
|
+
|
22
|
+
def error_message
|
23
|
+
@xml_data[root_element]['Error']['Message']
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_error
|
27
|
+
self.class.const_get(error_code =~ /Error$/ ? error_code : "#{error_code}Error").new error_message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SimpleQS
|
2
|
+
class Responce
|
3
|
+
class SuccessfulBuilder
|
4
|
+
def self.build responce
|
5
|
+
responce.instance_eval do
|
6
|
+
|
7
|
+
def action_name
|
8
|
+
@action_name ||= root_element.gsub(/Response/, '')
|
9
|
+
end
|
10
|
+
|
11
|
+
def request_id
|
12
|
+
@xml_data[root_element]['ResponseMetadata']['RequestId']
|
13
|
+
end
|
14
|
+
|
15
|
+
@xml_data[root_element]["#{action_name}Result"].each do |key, value|
|
16
|
+
self.instance_eval %{
|
17
|
+
def #{key.gsub(/([A-Z]+)/, '_\1').downcase.gsub(/^_/, '')}
|
18
|
+
#{value.inspect}
|
19
|
+
end
|
20
|
+
}
|
21
|
+
end if @xml_data[root_element]["#{action_name}Result"]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/version.rb
ADDED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 2
|
9
|
+
version: 0.1.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- !binary |
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2010-08-
|
20
|
+
date: 2010-08-17 00:00:00 +03:00
|
21
21
|
default_executable:
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|
@@ -48,6 +48,18 @@ dependencies:
|
|
48
48
|
version: 0.3.2
|
49
49
|
type: :runtime
|
50
50
|
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: rspec
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
51
63
|
description: SimpleQS library fully wraps Amazon SQS REST API. It allows you perform all kind of calls on queues and messages.
|
52
64
|
email: krekoten@gmail.com
|
53
65
|
executables: []
|
@@ -56,8 +68,23 @@ extensions: []
|
|
56
68
|
|
57
69
|
extra_rdoc_files: []
|
58
70
|
|
59
|
-
files:
|
60
|
-
|
71
|
+
files:
|
72
|
+
- lib/simple_qs/message.rb
|
73
|
+
- lib/simple_qs/queue.rb
|
74
|
+
- lib/simple_qs/request/base.rb
|
75
|
+
- lib/simple_qs/request/get.rb
|
76
|
+
- lib/simple_qs/request/post.rb
|
77
|
+
- lib/simple_qs/request.rb
|
78
|
+
- lib/simple_qs/responce/exceptions.rb
|
79
|
+
- lib/simple_qs/responce/failure_builder.rb
|
80
|
+
- lib/simple_qs/responce/successful_builder.rb
|
81
|
+
- lib/simple_qs/responce.rb
|
82
|
+
- lib/simple_qs.rb
|
83
|
+
- lib/version.rb
|
84
|
+
- LICENSE
|
85
|
+
- README.md
|
86
|
+
- CHANGELOG.md
|
87
|
+
- TODO.md
|
61
88
|
has_rdoc: true
|
62
89
|
homepage: http://github.com/krekoten/SimpleQS
|
63
90
|
licenses: []
|