aliyun-mqs 0.0.3 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ module Aliyun::Mqs
2
+ class Message
3
+
4
+ attr_reader :queue, :id, :body_md5, :body, :receipt_handle, :enqueue_at, :first_enqueue_at, :next_visible_at, :dequeue_count, :priority
5
+
6
+ def initialize queue, content
7
+ h = Hash.xml_object(content, "Message")
8
+ @queue = queue
9
+ @id = h["MessageId"]
10
+ @body_md5 = h["MessageBodyMD5"]
11
+ @body = h["MessageBody"]
12
+ @enqueue_at = Time.at(h["EnqueueTime"].to_i/1000.0)
13
+ @first_enqueue_at = Time.at(h["FirstDequeueTime"].to_i/1000.0)
14
+ @next_visible_at = Time.at(h["NextVisibleTime"].to_i/1000.0) if h["NextVisibleTime"]
15
+ @dequeue_count = h["DequeueCount"].to_i
16
+ @priority = h["Priority"].to_i
17
+ @receipt_handle = h["ReceiptHandle"]
18
+ end
19
+
20
+ def delete
21
+ check_receipt_handle
22
+ Aliyun::Mqs::Request.delete(queue.messages_path, params:{:ReceiptHandle => receipt_handle})
23
+ end
24
+
25
+ def change_visibility seconds
26
+ check_receipt_handle
27
+ Aliyun::Mqs::Request.put(queue.messages_path, params:{:ReceiptHandle => receipt_handle, :VisibilityTimeout=>seconds})
28
+ end
29
+
30
+ def to_s
31
+ s = {
32
+ "队列"=> queue.name,
33
+ "ID"=>id,
34
+ "MD5"=>body_md5,
35
+ "Receipt handle"=>receipt_handle,
36
+ "Enqueue at"=>enqueue_at,
37
+ "First enqueue at"=>first_enqueue_at,
38
+ "Next visible at"=>next_visible_at,
39
+ "Dequeue count" => dequeue_count,
40
+ "Priority"=>priority
41
+ }.collect{|k,v| "#{k}: #{v}"}
42
+
43
+ sep = "============================================="
44
+ s.unshift sep
45
+ s << sep
46
+ s << body
47
+ s.join("\n")
48
+ end
49
+
50
+ private
51
+ def check_receipt_handle
52
+ raise "No receipt handle for this operation" unless receipt_handle
53
+ end
54
+
55
+ end
56
+ end
@@ -1,77 +1,67 @@
1
- require 'active_support/core_ext/hash'
2
- require 'builder'
3
- require 'aliyun/mqs/http'
1
+ module Aliyun::Mqs
2
+ class Queue
3
+ attr_reader :name
4
4
 
5
- module Aliyun
6
- module Mqs
5
+ delegate :to_s, to: :name
7
6
 
8
- class Queue
9
- include Mqs::Http
10
-
11
- def initialize(name, access_owner_id: nil)
12
- @access_key_id = Mqs.configuration.access_key_id
13
- @access_key_secret = Mqs.configuration.access_key_secret
14
- @access_region = Mqs.configuration.access_region
15
- @access_owner_id = access_owner_id || Mqs.configuration.access_owner_id
16
- @access_queue = name
17
- @access_host = "#{@access_owner_id}.mqs-#{@access_region}.aliyuncs.com"
18
- throw '参数不能为nil' if instance_variables.any? {|x| x == nil}
7
+ class << self
8
+ def [] name
9
+ Queue.new(name)
19
10
  end
20
11
 
21
-
22
- def destroy
23
- verb = 'DELETE'
24
- request_resource = "/#{@access_queue}"
25
- request_uri = "http://#{@access_host}#{request_resource}"
26
- send_request(verb, request_uri)
12
+ def queues opts={}
13
+ mqs_options = {query: "x-mqs-prefix", offset: "x-mqs-marker", size: "x-mqs-ret-number"}
14
+ mqs_headers = opts.slice(*mqs_options.keys).reduce({}){|mqs_headers, item| k, v = *item; mqs_headers.merge!(mqs_options[k]=>v)}
15
+ response = Request.get("/", mqs_headers: mqs_headers)
16
+ Hash.xml_array(response, "Queues", "Queue").collect{|item| Queue.new(URI(item["QueueURL"]).path.sub!(/^\//, ""))}
27
17
  end
18
+ end
28
19
 
29
- def send(message_body, delay_seconds: 0, priority: 8)
30
- verb = 'POST'
31
- content_body = to_xml(message_body, delay_seconds, priority)
32
- request_resource = "/#{@access_queue}/messages"
33
- request_uri = "http://#{@access_host}#{request_resource}"
34
- send_request(verb, request_uri, content_body)
35
- end
20
+ def initialize name
21
+ @name = name
22
+ end
36
23
 
37
- def receive(waitseconds: nil, peekonly: false)
38
- verb = 'GET'
39
- query_params = {}
40
- query_params[:waitseconds] = waitseconds if waitseconds
41
- query_params[:peekonly] = true if peekonly # Aliyun doesn't accept uncessary query params
42
- request_resource = "/#{@access_queue}/messages" + (query_params.length > 0 ? '?' + query_params.to_param : '')
43
- request_uri = "http://#{@access_host}#{request_resource}"
44
- send_request(verb, request_uri)
24
+ def create opts={}
25
+ response = Request.put(queue_path) do |request|
26
+ msg_options = {
27
+ :VisibilityTimeout => 30,
28
+ :DelaySeconds => 0,
29
+ :MaximumMessageSize => 65536,
30
+ :MessageRetentionPeriod => 345600,
31
+ :PollingWaitSeconds => 0}.merge(opts)
32
+ request.content :Queue, msg_options
45
33
  end
34
+ end
46
35
 
47
- def delete message
48
- verb = 'DELETE'
49
- if String === message
50
- receipt_handle = message
51
- elsif Response === message
52
- receipt_handle = message.receipt_handle
53
- end
54
- request_resource = "/#{@access_queue}/messages?" + {ReceiptHandle: receipt_handle}.to_param
55
- request_uri = "http://#{@access_host}#{request_resource}"
56
- send_request(verb, request_uri)
57
- end
36
+ def delete
37
+ Request.delete(queue_path)
38
+ end
58
39
 
59
- def peek(waitseconcds: nil)
60
- receive(waitseconds: waitseconcds, peekonly: true)
40
+ def send_message message, opts={}
41
+ Request.post(messages_path) do |request|
42
+ msg_options = {:DelaySeconds => 0, :Priority => 10}.merge(opts)
43
+ request.content :Message, msg_options.merge(:MessageBody => message.to_s)
61
44
  end
45
+ end
62
46
 
63
- private
47
+ def receive_message wait_seconds: nil
48
+ request_opts = {}
49
+ request_opts.merge!(params:{waitseconds: wait_seconds}) if wait_seconds
50
+ result = Request.get(messages_path, request_opts)
51
+ Message.new(self, result)
52
+ end
64
53
 
65
- def to_xml(message_body, delay_seconds, priority)
66
- xml = Builder::XmlMarkup.new( :indent => 2 )
67
- xml.instruct! :xml, :encoding => 'UTF-8'
68
- xml.Message(:xmlns => 'http://mqs.aliyuncs.com/doc/v1/') do |m|
69
- m.MessageBody message_body
70
- m.DelaySeconds delay_seconds
71
- m.Priority priority
72
- end
73
- end
54
+ def peek_message
55
+ result = Request.get(messages_path, params: {peekonly: true})
56
+ Message.new(self, result)
57
+ end
58
+
59
+ def queue_path
60
+ "/#{name}"
61
+ end
74
62
 
63
+ def messages_path
64
+ "/#{name}/messages"
75
65
  end
76
66
 
77
67
  end
@@ -0,0 +1,88 @@
1
+ require 'base64'
2
+ module Aliyun::Mqs
3
+
4
+ class RequestException < Exception
5
+ attr_reader :content
6
+ delegate :[], to: :content
7
+
8
+ def initialize ex
9
+ @content = Hash.xml_object(ex.to_s, "Error")
10
+ rescue
11
+ @content = {"Message" => ex.message}
12
+ end
13
+ end
14
+
15
+ class Request
16
+ attr_reader :uri, :method, :date, :body, :content_md5, :content_type, :content_length, :mqs_headers
17
+ delegate :access_id, :key, :owner_id, :region, to: :configuration
18
+
19
+ class << self
20
+ [:get, :delete, :put, :post].each do |m|
21
+ define_method m do |*args, &block|
22
+ options = {method: m, path: args[0], mqs_headers: {}, params: {}}
23
+ options.merge!(args[1]) if args[1].is_a?(Hash)
24
+
25
+ request = Aliyun::Mqs::Request.new(options)
26
+ block.call(request) if block
27
+ request.execute
28
+ end
29
+ end
30
+ end
31
+
32
+ def initialize method: "get", path: "/", mqs_headers: {}, params: {}
33
+ conf = {
34
+ host: "#{owner_id}.mqs-#{region}.aliyuncs.com",
35
+ path: path
36
+ }
37
+ conf.merge!(query: params.to_query) unless params.empty?
38
+ @uri = URI::HTTP.build(conf)
39
+ @method = method
40
+ @mqs_headers = mqs_headers.merge("x-mqs-version" => "2014-07-08")
41
+ end
42
+
43
+ def content type, values={}
44
+ ns = "http://mqs.aliyuncs.com/doc/v1/"
45
+ builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
46
+ xml.send(type.to_sym, xmlns: ns) do |b|
47
+ values.each{|k,v| b.send k.to_sym, v}
48
+ end
49
+ end
50
+ @body = builder.to_xml
51
+ @content_md5 = Base64::encode64(Digest::MD5.hexdigest(body)).chop
52
+ @content_length = body.size
53
+ @content_type = "text/xml;charset=utf-8"
54
+ end
55
+
56
+ def execute
57
+ date = DateTime.now.httpdate
58
+ headers = {
59
+ "Authorization" => authorization(date),
60
+ "Content-Length" => content_length || 0,
61
+ "Content-Type" => content_type,
62
+ "Content-MD5" => content_md5,
63
+ "Date" => date,
64
+ "Host" => uri.host
65
+ }.merge(mqs_headers).reject{|k,v| v.nil?}
66
+ begin
67
+ RestClient.send *[method, uri.to_s, body, headers].compact
68
+ rescue RestClient::Exception => ex
69
+ raise RequestException.new(ex)
70
+ end
71
+ end
72
+
73
+ private
74
+ def configuration
75
+ Aliyun::Mqs.configuration
76
+ end
77
+
78
+ def authorization date
79
+ canonical_resource = [uri.path, uri.query].compact.join("?")
80
+ canonical_mq_headers = mqs_headers.sort.collect{|k,v| "#{k.downcase}:#{v}"}.join("\n")
81
+ method = self.method.to_s.upcase
82
+ signature = [method, content_md5 || "" , content_type || "" , date, canonical_mq_headers, canonical_resource].join("\n")
83
+ sha1 = Digest::HMAC.digest(signature, key, Digest::SHA1)
84
+ "MQS #{access_id}:#{Base64.encode64(sha1).chop}"
85
+ end
86
+
87
+ end
88
+ end
@@ -1,5 +1,5 @@
1
1
  module Aliyun
2
2
  module Mqs
3
- VERSION = "0.0.3"
3
+ VERSION = "0.1.1"
4
4
  end
5
5
  end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aliyun::Mqs::Queue do
4
+
5
+ let(:xml_message){
6
+ Aliyun::Mqs::Message.new(Aliyun::Mqs::Queue["aQueue"], <<-XML)
7
+ <?xml version="1.0" encoding="UTF-8" ?>
8
+ <Message xmlns="http://mqs.aliyuncs.com/doc/v1/">
9
+ <MessageId>5fea7756-0ea4-451a-a703-a558b933e274</MessageId>
10
+ <ReceiptHandle>MbZj6wDWli+QEauMZc8ZRv37sIW2iJKq3M9Mx/KSbkJ0</ReceiptHandle>
11
+ <MessageBodyMD5>fafb00f5732ab283681e124bf8747ed1</MessageBodyMD5>
12
+ <MessageBody>This is a test message</MessageBody>
13
+ <EnqueueTime>1250700979248000</EnqueueTime>
14
+ <NextVisibleTime>1250700799348000</NextVisibleTime>
15
+ <FirstDequeueTime>1250700779318000</FirstDequeueTime >
16
+ <DequeueCount>1</DequeueCount >
17
+ <Priority>8</Priority>
18
+ </Message>
19
+ XML
20
+ }
21
+
22
+ let(:peek_xml_message){
23
+ Aliyun::Mqs::Message.new(Aliyun::Mqs::Queue["aQueue"], <<-XML)
24
+ <?xml version="1.0" encoding="UTF-8" ?>
25
+ <Message xmlns="http://mqs.aliyuncs.com/doc/v1/">
26
+ <MessageId>5fea7756-0ea4-451a-a703-a558b933e274</MessageId>
27
+ <MessageBodyMD5>fafb00f5732ab283681e124bf8747ed1</MessageBodyMD5>
28
+ <MessageBody>This is a test message</MessageBody>
29
+ <EnqueueTime>1250700979248000</EnqueueTime>
30
+ <FirstDequeueTime>1250700979348000</FirstDequeueTime>
31
+ <DequeueCount>5</DequeueCount>
32
+ <Priority>8</Priority>
33
+ </Message>
34
+ XML
35
+ }
36
+
37
+ describe "#delete" do
38
+ specify "will delete the message from queue" do
39
+ expect(Aliyun::Mqs::Request).to receive(:delete).with("/aQueue/messages", params:{:ReceiptHandle=>"MbZj6wDWli+QEauMZc8ZRv37sIW2iJKq3M9Mx/KSbkJ0"})
40
+ xml_message.delete
41
+ end
42
+
43
+ specify "won't delete message without receipt_handle" do
44
+ expect{peek_xml_message.delete}.to raise_exception
45
+ end
46
+ end
47
+
48
+ describe "#change_visibility" do
49
+ specify "will change message's visibility timeout" do
50
+ expect(Aliyun::Mqs::Request).to receive(:put).with("/aQueue/messages", params:{
51
+ :ReceiptHandle=>"MbZj6wDWli+QEauMZc8ZRv37sIW2iJKq3M9Mx/KSbkJ0",
52
+ :VisibilityTimeout => 10
53
+ })
54
+
55
+ xml_message.change_visibility 10
56
+ end
57
+
58
+ specify "won't change message's visibility timeout given message has no receipt_handle" do
59
+ expect{peek_xml_message.change_visibility 10}.to raise_exception
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,179 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aliyun::Mqs::Queue do
4
+
5
+ specify ".[] will create new queue instance" do
6
+ queue = Aliyun::Mqs::Queue["aQueue"]
7
+ expect(queue).not_to be_nil
8
+ expect(queue.name).to eq("aQueue")
9
+ end
10
+
11
+ describe ".queues" do
12
+ let(:xml_response){
13
+ <<-XML
14
+ <?xml version="1.0"?>
15
+ <Queues xmlns="http://mqs.aliyuncs.com/doc/v1">
16
+ <Queue>
17
+ <QueueURL>http://xxxxx.mqs-cn-hangzhou.aliyuncs.com/test</QueueURL>
18
+ </Queue>
19
+ </Queues>
20
+ XML
21
+ }
22
+
23
+ specify "find all queues" do
24
+ expect(Aliyun::Mqs::Request).to receive(:get).with("/", mqs_headers:{}).and_return xml_response
25
+ queues = Aliyun::Mqs::Queue.queues
26
+ expect(queues.size).to eq(1)
27
+ expect(queues[0].name).to eq("test")
28
+ end
29
+
30
+ specify "query queues" do
31
+ expect(Aliyun::Mqs::Request).to receive(:get).with("/", mqs_headers:{"x-mqs-prefix"=>"query"}).and_return xml_response
32
+ queues = Aliyun::Mqs::Queue.queues(query: "query")
33
+ end
34
+
35
+ specify "find number of queues" do
36
+ expect(Aliyun::Mqs::Request).to receive(:get).with("/", mqs_headers:{"x-mqs-ret-number"=>5}).and_return xml_response
37
+ queues = Aliyun::Mqs::Queue.queues(size: 5)
38
+ end
39
+
40
+ specify "find of queues start at given position" do
41
+ expect(Aliyun::Mqs::Request).to receive(:get).with("/", mqs_headers:{"x-mqs-marker"=>2}).and_return xml_response
42
+ queues = Aliyun::Mqs::Queue.queues(offset: 2)
43
+ end
44
+ end
45
+
46
+
47
+ describe "#create" do
48
+ specify "will create a new queue with default options" do
49
+ expect(RestClient).to receive(:put) do |*args|
50
+ path, body, headers = *args
51
+ expect(path).to eq("http://owner-id.mqs-region.aliyuncs.com/aQueue")
52
+ xml = Hash.from_xml(body)
53
+ expect(xml["Queue"]["VisibilityTimeout"]).to eq("30")
54
+ expect(xml["Queue"]["DelaySeconds"]).to eq("0")
55
+ expect(xml["Queue"]["MaximumMessageSize"]).to eq("65536")
56
+ expect(xml["Queue"]["MessageRetentionPeriod"]).to eq("345600")
57
+ expect(xml["Queue"]["PollingWaitSeconds"]).to eq("0")
58
+ expect(headers).not_to be_nil
59
+ end
60
+ Aliyun::Mqs::Queue["aQueue"].create
61
+ end
62
+
63
+ specify "will create a new queue with customized options" do
64
+ expect(RestClient).to receive(:put) do |*args|
65
+ path, body, headers = *args
66
+ expect(Hash.from_xml(body)["Queue"]["PollingWaitSeconds"]).to eq("30")
67
+ end
68
+ Aliyun::Mqs::Queue["aQueue"].create(:PollingWaitSeconds => 30)
69
+ end
70
+ end
71
+
72
+ describe "#delete" do
73
+ specify "will delete existing queue" do
74
+ expect(Aliyun::Mqs::Request).to receive(:delete).with("/aQueue")
75
+ Aliyun::Mqs::Queue["aQueue"].delete
76
+ end
77
+ end
78
+
79
+ describe "#send_message" do
80
+ specify "will send a message to a queue with default options" do
81
+ expect(RestClient).to receive(:post) do |*args|
82
+ path, body, headers = *args
83
+ expect(path).to eq("http://owner-id.mqs-region.aliyuncs.com/aQueue/messages")
84
+ xml = Hash.from_xml(body)
85
+ expect(xml["Message"]["MessageBody"]).to eq("text message")
86
+ expect(xml["Message"]["DelaySeconds"]).to eq("0")
87
+ expect(xml["Message"]["Priority"]).to eq("10")
88
+ expect(headers).not_to be_nil
89
+ end
90
+
91
+ Aliyun::Mqs::Queue["aQueue"].send_message "text message"
92
+ end
93
+
94
+
95
+ specify "will send a message to a queue with customized options" do
96
+ expect(RestClient).to receive(:post) do |*args|
97
+ path, body, headers = *args
98
+ expect(Hash.from_xml(body)["Message"]["Priority"]).to eq("1")
99
+ end
100
+
101
+ Aliyun::Mqs::Queue["aQueue"].send_message "text message", :Priority=>1
102
+ end
103
+ end
104
+
105
+
106
+ describe "#receive_message" do
107
+ let(:xml_response){
108
+ <<-XML
109
+ <?xml version="1.0" encoding="UTF-8" ?>
110
+ <Message xmlns="http://mqs.aliyuncs.com/doc/v1/">
111
+ <MessageId>5fea7756-0ea4-451a-a703-a558b933e274</MessageId>
112
+ <ReceiptHandle>MbZj6wDWli+QEauMZc8ZRv37sIW2iJKq3M9Mx/KSbkJ0</ReceiptHandle>
113
+ <MessageBodyMD5>fafb00f5732ab283681e124bf8747ed1</MessageBodyMD5>
114
+ <MessageBody>This is a test message</MessageBody>
115
+ <EnqueueTime>1250700979248000</EnqueueTime>
116
+ <NextVisibleTime>1250700799348000</NextVisibleTime>
117
+ <FirstDequeueTime>1250700779318000</FirstDequeueTime >
118
+ <DequeueCount>1</DequeueCount >
119
+ <Priority>8</Priority>
120
+ </Message>
121
+ XML
122
+ }
123
+
124
+ specify "will receive message from a queue" do
125
+ expect(Aliyun::Mqs::Request).to receive(:get).with("/aQueue/messages",{}).and_return xml_response
126
+
127
+ message = Aliyun::Mqs::Queue["aQueue"].receive_message
128
+ expect(message).not_to be_nil
129
+ expect(message.id).to eq("5fea7756-0ea4-451a-a703-a558b933e274")
130
+ expect(message.body).to eq("This is a test message")
131
+ expect(message.body_md5).to eq("fafb00f5732ab283681e124bf8747ed1")
132
+ expect(message.receipt_handle).to eq("MbZj6wDWli+QEauMZc8ZRv37sIW2iJKq3M9Mx/KSbkJ0")
133
+ expect(message.enqueue_at).to eq(Time.at(1250700979248000/1000.0))
134
+ expect(message.first_enqueue_at).to eq(Time.at(1250700779318000/1000.0))
135
+ expect(message.next_visible_at).to eq(Time.at(1250700799348000/1000.0))
136
+ expect(message.dequeue_count).to eq(1)
137
+ expect(message.priority).to eq(8)
138
+ end
139
+
140
+ specify "will receive message from a queue with poll wait" do
141
+ expect(Aliyun::Mqs::Request).to receive(:get).with("/aQueue/messages",params:{waitseconds: 60}).and_return xml_response
142
+ message = Aliyun::Mqs::Queue["aQueue"].receive_message wait_seconds: 60
143
+ end
144
+ end
145
+
146
+ describe "#peek" do
147
+ let(:xml_response){
148
+ <<-XML
149
+ <?xml version="1.0" encoding="UTF-8" ?>
150
+ <Message xmlns="http://mqs.aliyuncs.com/doc/v1/">
151
+ <MessageId>5fea7756-0ea4-451a-a703-a558b933e274</MessageId>
152
+ <MessageBodyMD5>fafb00f5732ab283681e124bf8747ed1</MessageBodyMD5>
153
+ <MessageBody>This is a test message</MessageBody>
154
+ <EnqueueTime>1250700979248000</EnqueueTime>
155
+ <FirstDequeueTime>1250700979348000</FirstDequeueTime>
156
+ <DequeueCount>5</DequeueCount>
157
+ <Priority>8</Priority>
158
+ </Message>
159
+ XML
160
+ }
161
+
162
+ specify "will peek message of a queue" do
163
+ expect(Aliyun::Mqs::Request).to receive(:get).with("/aQueue/messages",params:{peekonly: true}).and_return xml_response
164
+ message = Aliyun::Mqs::Queue["aQueue"].peek_message
165
+
166
+ expect(message).not_to be_nil
167
+ expect(message.id).to eq("5fea7756-0ea4-451a-a703-a558b933e274")
168
+ expect(message.body).to eq("This is a test message")
169
+ expect(message.body_md5).to eq("fafb00f5732ab283681e124bf8747ed1")
170
+ expect(message.receipt_handle).to be_nil
171
+ expect(message.enqueue_at).to eq(Time.at(1250700979248000/1000.0))
172
+ expect(message.first_enqueue_at).to eq(Time.at(1250700979348000/1000.0))
173
+ expect(message.next_visible_at).to be_nil
174
+ expect(message.dequeue_count).to eq(5)
175
+ expect(message.priority).to eq(8)
176
+ end
177
+ end
178
+
179
+ end