├── sample.cfg
├── .gitignore
├── sample
├── deletequeue.py
├── deletetopic.py
├── createqueue.py
├── createtopic.py
├── sendmessage.py
├── subscribe.py
├── publishmessage.py
├── subscribe_queueendpoint.py
├── recvdelmessage.py
├── simple_http_notify_endpoint.py
├── sample_common.py
├── simple_https_notify_endpoint.py
└── server.py
├── test
├── model
│ ├── test_topic_message_base64.py
│ └── __init__.py
├── __init__.py
└── client
│ ├── __init__.py
│ ├── test_send_message.py
│ ├── test_peek_message.py
│ └── test_receive_message.py
├── mns
├── __init__.py
├── pkg_info.py
├── mns_common.py
├── mns_exception.py
├── mns_http.py
├── subscription.py
├── account.py
├── topic.py
├── mns_request.py
├── mns_tool.py
└── queue.py
├── setup.py
├── README.md
├── LICENSE
└── sample.py
/sample.cfg:
--------------------------------------------------------------------------------
1 | [Base]
2 | Endpoint =
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .idea
3 | .project
4 | .pydevproject
5 | .settings
6 | .coverage
7 | build
8 | dist
9 | .vs/
--------------------------------------------------------------------------------
/sample/deletequeue.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 |
4 | import sys
5 | import os
6 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/..")
7 |
8 | import time
9 | from sample_common import MNSSampleCommon
10 | from mns.account import Account
11 | from mns.queue import *
12 |
13 | #从sample.cfg中读取基本配置信息
14 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information: https://yq.aliyun.com/articles/55947)
15 | accid,acckey,endpoint,token = MNSSampleCommon.LoadConfig()
16 |
17 | #初始化 my_account, my_queue
18 | my_account = Account(endpoint, accid, acckey, token)
19 | queue_name = sys.argv[1] if len(sys.argv) > 1 else "MySampleQueue"
20 | my_queue = my_account.get_queue(queue_name)
21 |
22 | #删除队列
23 | try:
24 | my_queue.delete()
25 | print("Delete Queue Succeed! QueueName:%s\n" % queue_name)
26 | except MNSExceptionBase as e:
27 | print("Delete Queue Fail! Exception:%s\n" % e)
28 |
--------------------------------------------------------------------------------
/sample/deletetopic.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 |
4 | import sys
5 | import os
6 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/..")
7 |
8 | import time
9 | from sample_common import MNSSampleCommon
10 | from mns.account import Account
11 | from mns.topic import *
12 |
13 | #从sample.cfg中读取基本配置信息
14 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information: https://yq.aliyun.com/articles/55947)
15 | accid,acckey,endpoint,token = MNSSampleCommon.LoadConfig()
16 |
17 | #初始化 my_account, my_topic
18 | my_account = Account(endpoint, accid, acckey, token)
19 | topic_name = sys.argv[1] if len(sys.argv) > 1 else "MySampleTopic"
20 | my_topic = my_account.get_topic(topic_name)
21 |
22 | #删除主题
23 | try:
24 | my_topic.delete()
25 | print("Delete Topic Succeed! TopicName:%s\n" % topic_name)
26 | except MNSExceptionBase as e:
27 | print("Delete Topic Fail! Exception:%s\n" % e)
28 |
--------------------------------------------------------------------------------
/test/model/test_topic_message_base64.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf-8
3 | import base64
4 | import unittest
5 |
6 | from mns.topic import TopicMessage, Base64TopicMessage
7 |
8 |
9 | class TestTopicMessageBase64(unittest.TestCase):
10 |
11 | def test_message(self):
12 | message_body = u"test 字符串"
13 | topic_message = TopicMessage(message_body)
14 | base64_message = Base64TopicMessage(message_body)
15 | # 验证消息体是否正确设置, 消息体为 原值
16 | self.assertEqual(message_body, topic_message.message_body)
17 | # 验证消息体是否正确获取
18 | self.assertEqual(message_body, topic_message.get_messagebody())
19 | # 验证消息体是否正确设置, 消息体为 base64编码后 的字符串
20 | self.assertEqual(base64.b64encode(message_body.encode("utf-8")).decode("utf-8"), base64_message.message_body)
21 | # 验证消息体是否正确获取
22 | self.assertEqual(message_body, base64_message.get_messagebody())
23 |
24 |
25 | if __name__ == '__main__':
26 | unittest.main()
--------------------------------------------------------------------------------
/mns/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015, Alibaba Cloud Computing
2 |
3 | #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:
4 |
5 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | #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.
8 |
9 |
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015, Alibaba Cloud Computing
2 |
3 | #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:
4 |
5 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | #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.
8 |
9 |
--------------------------------------------------------------------------------
/test/model/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015, Alibaba Cloud Computing
2 |
3 | #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:
4 |
5 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | #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.
8 |
9 |
--------------------------------------------------------------------------------
/test/client/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015, Alibaba Cloud Computing
2 |
3 | #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:
4 |
5 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | #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.
8 |
9 |
--------------------------------------------------------------------------------
/sample/createqueue.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 |
4 | import sys
5 | import os
6 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/..")
7 |
8 | import time
9 | from sample_common import MNSSampleCommon
10 | from mns.account import Account
11 | from mns.queue import *
12 |
13 | #从sample.cfg中读取基本配置信息
14 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information: https://yq.aliyun.com/articles/55947)
15 | accid,acckey,endpoint,token = MNSSampleCommon.LoadConfig()
16 |
17 | #初始化 my_account, my_queue
18 | my_account = Account(endpoint, accid, acckey, token)
19 | queue_name = sys.argv[1] if len(sys.argv) > 1 else "MySampleQueue"
20 | my_queue = my_account.get_queue(queue_name)
21 |
22 | #创建队列, 具体属性请参考mns/queue.py中的QueueMeta结构
23 | queue_meta = QueueMeta()
24 | try:
25 | queue_url = my_queue.create(queue_meta)
26 | print ("Create Queue Succeed! QueueName:%s\n" % queue_name)
27 | except MNSExceptionBase as e:
28 | if e.type == "QueueAlreadyExist":
29 | print ("Queue already exist, please delete it before creating or use it directly.")
30 | sys.exit(0)
31 | print ("Create Queue Fail! Exception:%s\n" % e)
32 |
--------------------------------------------------------------------------------
/sample/createtopic.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 |
4 | import sys
5 | import os
6 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/..")
7 |
8 | import time
9 | from sample_common import MNSSampleCommon
10 | from mns.account import Account
11 | from mns.topic import *
12 |
13 | #从sample.cfg中读取基本配置信息
14 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information: https://yq.aliyun.com/articles/55947)
15 | accid,acckey,endpoint,token = MNSSampleCommon.LoadConfig()
16 |
17 | #初始化 my_account, my_topic
18 | my_account = Account(endpoint, accid, acckey, token)
19 | topic_name = sys.argv[1] if len(sys.argv) > 1 else "MySampleTopic"
20 | my_topic = my_account.get_topic(topic_name)
21 |
22 | #创建主题, 具体属性请参考mns/topic.py中的TopicMeta结构
23 | topic_meta = TopicMeta()
24 | try:
25 | topic_url = my_topic.create(topic_meta)
26 | print("Create Topic Succeed! TopicName:%s\n" % topic_name)
27 | except MNSExceptionBase as e:
28 | if e.type == "TopicAlreadyExist":
29 | print("Topic already exist, please delete it before creating or use it directly.")
30 | sys.exit(0)
31 | print("Create Topic Fail! Exception:%s\n" % e)
32 |
33 |
--------------------------------------------------------------------------------
/sample/sendmessage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 |
4 | import sys
5 | import os
6 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/..")
7 |
8 | import time
9 | from sample_common import MNSSampleCommon
10 | from mns.account import Account
11 | from mns.queue import *
12 |
13 | #从sample.cfg中读取基本配置信息
14 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information: https://yq.aliyun.com/articles/55947)
15 | accid,acckey,endpoint,token = MNSSampleCommon.LoadConfig()
16 |
17 | #初始化 my_account, my_queue
18 | my_account = Account(endpoint, accid, acckey, token)
19 | queue_name = sys.argv[1] if len(sys.argv) > 1 else "MySampleQueue"
20 | my_queue = my_account.get_queue(queue_name)
21 |
22 | #循环发送多条消息
23 | msg_count = 3
24 |
25 | print("%sSend Message To Queue%s\nQueueName:%s\nMessageCount:%s\n" % (10*"=", 10*"=", queue_name, msg_count))
26 | for i in range(msg_count):
27 | try:
28 | msg_body = u"I am test message %s." % i
29 | msg = Message(msg_body)
30 | re_msg = my_queue.send_message(msg)
31 | print("Send Message Succeed! MessageBody:%s MessageID:%s ReceiptHandle:%s" % (msg_body, re_msg.message_id, re_msg.receipt_handle))
32 | except MNSExceptionBase as e:
33 | if e.type == "QueueNotExist":
34 | print("Queue not exist, please create queue before send message.")
35 | sys.exit(0)
36 | print("Send Message Fail! Exception:%s\n" % e)
37 |
--------------------------------------------------------------------------------
/test/client/test_send_message.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf-8
3 | import unittest
4 |
5 | from mns.mns_xml_handler import *
6 |
7 |
8 | class SendMessageTest(unittest.TestCase):
9 |
10 | def test_send_base64_message(self):
11 | # 构建 request, 模拟 进行base64编码 时, send_message 对于 消息体 的 encode()结果
12 | message_body = u"I am 测试字符串."
13 | queue_name = "test_queue"
14 | req = SendMessageRequest(queue_name, message_body, base64encode=True)
15 | # 生成 发送请求的请求体
16 | xml_data = MessageEncoder.encode(req)
17 | # 解析 请求体, 转换为 字典
18 | data_dic = {}
19 | DecoderBase.xml_to_dic("Message", xml_data, data_dic)
20 | # 进行 base64编码时,send_message 的 encode() 结果为 base64编码后的字符串
21 | self.assertEqual(base64.b64encode(message_body.encode("utf-8")).decode("utf-8"), data_dic["MessageBody"])
22 |
23 | def test_send_raw_message(self):
24 | # 构建 request, 模拟 不进行base64编码 时, send_message 对于 消息体 的 encode()结果
25 | message_body = u"I am 测试字符串."
26 | queue_name = "test_queue"
27 | req = SendMessageRequest(queue_name, message_body, base64encode=False)
28 | # 生成 发送请求的请求体
29 | xml_data = MessageEncoder.encode(req)
30 | # 解析 请求体, 转换为 字典
31 | data_dic = {}
32 | DecoderBase.xml_to_dic("Message", xml_data, data_dic)
33 | # 进行 base64编码时,send_message 的 encode() 结果为 原字符串
34 | self.assertEqual(message_body, data_dic["MessageBody"])
35 |
36 |
37 | if __name__ == '__main__':
38 | unittest.main()
39 |
--------------------------------------------------------------------------------
/mns/pkg_info.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015, Alibaba Cloud Computing
2 |
3 | #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:
4 |
5 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | #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.
8 |
9 | name = "aliyun-mns-sdk"
10 | version = "1.2.0"
11 | url = ""
12 | license = "MIT"
13 | short_description = "Aliyun Message Service Library"
14 | long_description = """
15 | Mns provides interfaces to Aliyun Message Service.
16 | mnscmd lets you do these actions: create/get/list/set/delete queue, send/receive/peek/change/delete message from/to queue, create/get/list/set/delete topic, publish message to topic, subscribe/get/list/set/unsubscribe subscription.
17 | """
18 |
--------------------------------------------------------------------------------
/sample/subscribe.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 |
4 | import sys
5 | import os
6 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/..")
7 |
8 | import time
9 | from sample_common import MNSSampleCommon
10 | from mns.account import Account
11 | from mns.topic import *
12 | from mns.subscription import *
13 |
14 | #参数合法性检查,订阅的Endpoint参数必须传入
15 | if len(sys.argv) < 2:
16 | print("Please specify endpoint. e.g. python subscribe.py http://127.0.0.1:80")
17 | sys.exit(1)
18 | sub_endpoint = sys.argv[1]
19 |
20 | #从sample.cfg中读取基本配置信息
21 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information: https://yq.aliyun.com/articles/55947)
22 | accid,acckey,endpoint,token = MNSSampleCommon.LoadConfig()
23 |
24 | #初始化 my_account, my_topic, my_sub
25 | my_account = Account(endpoint, accid, acckey, token)
26 |
27 | topic_name = sys.argv[2] if len(sys.argv) > 2 else "MySampleTopic"
28 | my_topic = my_account.get_topic(topic_name)
29 |
30 | sub_name = sys.argv[3] if len(sys.argv) > 3 else "MySampleTopic-Sub"
31 | my_sub = my_topic.get_subscription(sub_name)
32 |
33 | #创建订阅, 具体属性请参考mns/subscription.py中的SubscriptionMeta结构
34 | sub_meta = SubscriptionMeta(sub_endpoint)
35 | try:
36 | topic_url = my_sub.subscribe(sub_meta)
37 | print("Create Subscription Succeed! TopicName:%s SubName:%s Endpoint:%s\n" % (topic_name, sub_name, sub_endpoint))
38 | except MNSExceptionBase as e:
39 | if e.type == "TopicNotExist":
40 | print("Topic not exist, please create topic.")
41 | sys.exit(0)
42 | elif e.type == "SubscriptionAlreadyExist":
43 | print("Subscription already exist, please unsubscribe or use it directly.")
44 | sys.exit(0)
45 | print("Create Subscription Fail! Exception:%s\n" % e)
46 |
--------------------------------------------------------------------------------
/sample/publishmessage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 |
4 | import sys
5 | import os
6 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/..")
7 |
8 | import time
9 | from sample_common import MNSSampleCommon
10 | from mns.account import Account
11 | from mns.topic import *
12 |
13 | #从sample.cfg中读取基本配置信息
14 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information: https://yq.aliyun.com/articles/55947)
15 | accid,acckey,endpoint,token = MNSSampleCommon.LoadConfig()
16 |
17 | #初始化 my_account, my_topic
18 | my_account = Account(endpoint, accid, acckey, token)
19 | topic_name = sys.argv[1] if len(sys.argv) > 1 else "MySampleTopic"
20 | my_topic = my_account.get_topic(topic_name)
21 |
22 | #循环发布多条消息
23 | msg_count = 3
24 | print("%sPublish Message To Topic%s\nTopicName:%s\nMessageCount:%s\n" % (10*"=", 10*"=", topic_name, msg_count))
25 |
26 | for i in range(msg_count):
27 | try:
28 | msg_body = u"I am test message %s." % i
29 | msg = TopicMessage(msg_body)
30 | re_msg = my_topic.publish_message(msg)
31 | print("Publish Raw Message Succeed. MessageBody:%s MessageID:%s" % (msg_body, re_msg.message_id))
32 | except MNSExceptionBase as e:
33 | if e.type == "TopicNotExist":
34 | print("Topic not exist, please create it.")
35 | sys.exit(1)
36 | print("Publish Raw Message Fail. Exception:%s" % e)
37 |
38 | for i in range(msg_count):
39 | try:
40 | msg_body = u"I am test message %s." % i
41 | msg = Base64TopicMessage(msg_body)
42 | re_msg = my_topic.publish_message(msg)
43 | print("Publish Base64 Encoded Message Succeed. MessageBody:%s MessageID:%s" % (msg_body, re_msg.message_id))
44 | except MNSExceptionBase as e:
45 | if e.type == "TopicNotExist":
46 | print("Topic not exist, please create it.")
47 | sys.exit(1)
48 | print("Publish Base64 Encoded Message Fail. Exception:%s" % e)
49 |
--------------------------------------------------------------------------------
/sample/subscribe_queueendpoint.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 |
4 | import sys
5 | import os
6 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/..")
7 |
8 | import time
9 | from sample_common import MNSSampleCommon
10 | from mns.mns_common import *
11 | from mns.account import Account
12 | from mns.topic import *
13 | from mns.subscription import *
14 |
15 | if len(sys.argv) < 3:
16 | print("Please specify endpoint. e.g. python subscribe_queueendpoint.py cn-hanghzou MySampleSubQueue")
17 | sys.exit(1)
18 | region = sys.argv[1]
19 | queue_name = sys.argv[2]
20 |
21 | #从sample.cfg中读取基本配置信息
22 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information: https://yq.aliyun.com/articles/55947)
23 | accid,acckey,endpoint,token = MNSSampleCommon.LoadConfig()
24 | account_id = endpoint.split("/")[2].split(".")[0]
25 | queue_endpoint = TopicHelper.generate_queue_endpoint(region, account_id, queue_name)
26 |
27 | #初始化 my_account, my_topic, my_sub
28 | my_account = Account(endpoint, accid, acckey, token)
29 | topic_name = sys.argv[3] if len(sys.argv) > 3 else "MySampleTopic"
30 | my_topic = my_account.get_topic(topic_name)
31 |
32 | sub_name = sys.argv[4] if len(sys.argv) > 4 else "MySampleTopic-Sub"
33 | my_sub = my_topic.get_subscription(sub_name)
34 |
35 |
36 | #创建订阅, 具体属性请参考mns/subscription.py中的SubscriptionMeta结构
37 | sub_meta = SubscriptionMeta(queue_endpoint, notify_content_format = SubscriptionNotifyContentFormat.SIMPLIFIED)
38 | try:
39 | topic_url = my_sub.subscribe(sub_meta)
40 | print("Create Subscription Succeed! TopicName:%s SubName:%s Endpoint:%s\n" % (topic_name, sub_name, queue_endpoint))
41 | except MNSExceptionBase as e:
42 | if e.type == "TopicNotExist":
43 | print("Topic not exist, please create topic.")
44 | sys.exit(0)
45 | elif e.type == "SubscriptionAlreadyExist":
46 | print("Subscription already exist, please unsubscribe or use it directly.")
47 | sys.exit(0)
48 | print("Create Subscription Fail! Exception:%s\n" % e)
49 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | try:
11 | from setuptools import setup
12 | except ImportError:
13 | from distutils.core import setup
14 |
15 | import os
16 | import sys
17 | import mns.pkg_info
18 |
19 | if sys.version_info <= (2, 5):
20 | sys.stderr.write("ERROR: mns python sdk requires Python Version 2.5 or above.\n")
21 | sys.stderr.write("Your Python version is %s.%s.%s.\n" % sys.version_info[:3])
22 | sys.exit(1)
23 |
24 | requires = []
25 | requires.append("pycrypto")
26 | if sys.version_info < (3, 0):
27 | requires.append("aliyun-python-sdk-core>=2.0.2")
28 | else:
29 | requires.append("aliyun-python-sdk-core-v3>=2.3.5")
30 |
31 | setup( name = mns.pkg_info.name,
32 | version = mns.pkg_info.version,
33 | author = "Aliyun MNS",
34 | author_email = "",
35 | url = mns.pkg_info.url,
36 | packages = ["mns"],
37 | scripts = ["bin/mnscmd"],
38 | install_requires=requires,
39 | license = mns.pkg_info.license,
40 | description = mns.pkg_info.short_description,
41 | long_description = mns.pkg_info.long_description )
42 |
--------------------------------------------------------------------------------
/sample/recvdelmessage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 |
4 | import sys
5 | import os
6 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/..")
7 |
8 | import time
9 | from sample_common import MNSSampleCommon
10 | from mns.account import Account
11 | from mns.queue import *
12 |
13 | #从sample.cfg中读取基本配置信息
14 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information: https://yq.aliyun.com/articles/55947)
15 | accid,acckey,endpoint,token = MNSSampleCommon.LoadConfig()
16 |
17 | #初始化 my_account, my_queue
18 | my_account = Account(endpoint, accid, acckey, token)
19 | queue_name = sys.argv[1] if len(sys.argv) > 1 else "MySampleQueue"
20 | base64 = False if len(sys.argv) > 2 and sys.argv[2].lower() == "false" else True
21 | my_queue = my_account.get_queue(queue_name)
22 | my_queue.set_encoding(base64)
23 |
24 |
25 | #循环读取删除消息直到队列空
26 | #receive message请求使用long polling方式,通过wait_seconds指定长轮询时间为3秒
27 |
28 | ## long polling 解析:
29 | ### 当队列中有消息时,请求立即返回;
30 | ### 当队列中没有消息时,请求在MNS服务器端挂3秒钟,在这期间,有消息写入队列,请求会立即返回消息,3秒后,请求返回队列没有消息;
31 |
32 | wait_seconds = 3
33 | print("%sReceive And Delete Message From Queue%s\nQueueName:%s\nWaitSeconds:%s\n" % (10*"=", 10*"=", queue_name, wait_seconds))
34 | while True:
35 | #读取消息
36 | try:
37 | # receive_message 返回字节串,receive_message_with_str_body 返回字符串
38 | # recv_msg = my_queue.receive_message(wait_seconds)
39 | recv_msg = my_queue.receive_message_with_str_body(wait_seconds)
40 | print("Receive Message Succeed! ReceiptHandle:%s MessageBody:%s MessageID:%s" % (recv_msg.receipt_handle, recv_msg.message_body, recv_msg.message_id))
41 | except Exception as e:
42 | #except MNSServerException as e:
43 | if hasattr(e, 'type'):
44 | if e.type == u"QueueNotExist":
45 | print("Queue not exist, please create queue before receive message.")
46 | sys.exit(0)
47 | elif e.type == u"MessageNotExist":
48 | print("Queue is empty!")
49 | sys.exit(0)
50 | print("Receive Message Fail! Exception:%s\n" % e)
51 | continue
52 |
53 | #删除消息
54 | try:
55 | my_queue.delete_message(recv_msg.receipt_handle)
56 | print("Delete Message Succeed! ReceiptHandle:%s" % recv_msg.receipt_handle)
57 | except Exception as e:
58 | print("Delete Message Fail! Exception:%s\n" % e)
59 |
--------------------------------------------------------------------------------
/mns/mns_common.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | class RequestInfo:
11 | def __init__(self, request_id = None):
12 | """ this information will be send to MNS Server
13 | @note:
14 | :: request_id: used to search logs of this request
15 | """
16 | self.request_id = request_id
17 |
18 | class TopicHelper:
19 |
20 | @staticmethod
21 | def generate_queue_endpoint(region, accountid, queue_name):
22 | """
23 | @type region: string
24 | @param region: the region of queue, such as: cn-hangzhou
25 |
26 | @type accountid: string
27 | @param accountid: the accountid of queue's owner
28 |
29 | @type queue_name: string
30 | @param queue_name
31 | """
32 | return "acs:mns:%s:%s:queues/%s" % (region, accountid, queue_name)
33 |
34 | @staticmethod
35 | def generate_mail_endpoint(mail_address):
36 | """
37 | @type mail_address: string
38 | @param mail_address: the address of mail
39 | """
40 | return "mail:directmail:%s" % mail_address
41 |
42 | @staticmethod
43 | def generate_sms_endpoint(phone=None):
44 | """
45 | @type phone: string
46 | @param phone: the number of phone
47 | """
48 | endpoint = "sms:directsms:anonymous" if phone is None else "sms:directsms:%s" % phone
49 | return endpoint
50 |
--------------------------------------------------------------------------------
/sample/simple_http_notify_endpoint.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 | # Copyright (C) 2015, Alibaba Cloud Computing
4 |
5 | #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:
6 |
7 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | #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.
10 |
11 | import sys
12 | import cgi
13 | import shutil
14 | import socket
15 | import base64
16 | import logging
17 | try:
18 | from urllib.request import urlopen
19 | except ImportError:
20 | from urllib2 import urlopen
21 | except Exception as err:
22 | raise(err)
23 |
24 | import logging.handlers
25 | import xml.dom.minidom
26 | #import BaseHTTPServer
27 | try:
28 | from http.server import BaseHTTPRequestHandler, HTTPServer
29 | except ImportError:
30 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
31 | except Exception as err:
32 | raise(err)
33 |
34 | #import SocketServer
35 | try:
36 | import socketserver as SocketServer
37 | except ImportError:
38 | import SocketServer as SocketServer
39 |
40 | from sample_common import MNSSampleCommon
41 | import server
42 |
43 | __version__ = "1.0.3"
44 | _LOGGER = logging.getLogger(__name__)
45 |
46 | def main(ip_addr, port, endpoint_class = server.SimpleHttpNotifyEndpoint, msg_type=u"XML", prefix=u"http://"):
47 | #init logger
48 | global logger
49 | endpoint_class.access_log_file = "access_log.%s" % port
50 | endpoint_class.msg_type = msg_type
51 | log_file = "endpoint_log.%s" % port
52 | logger = logging.getLogger()
53 | file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100*1024*1024)
54 | formatter = logging.Formatter('[%(asctime)s] [%(name)s] [%(levelname)s] [%(filename)s:%(lineno)d] [%(thread)d] %(message)s', '%Y-%m-%d %H:%M:%S')
55 | file_handler.setFormatter(formatter)
56 | logger.addHandler(file_handler)
57 | logger.setLevel(logging.INFO)
58 |
59 | #start endpoint
60 | addr_info = "Start Endpoint! Address: %s%s:%s" % (prefix, ip_addr, port)
61 | print(addr_info)
62 | try:
63 | logger.info(addr_info)
64 | httpd = server.ThreadedHTTPServer(('', port), endpoint_class)
65 | httpd.serve_forever()
66 | except KeyboardInterrupt:
67 | print("Shutting down the simple notify endpoint!")
68 | httpd.socket.close()
69 |
70 | if __name__ == "__main__":
71 | ip_addr = MNSSampleCommon.LoadIndexParam(1)
72 | if not ip_addr:
73 | print("ERROR: Must specify IP Address")
74 | sys.exit(0)
75 |
76 | para_port = MNSSampleCommon.LoadIndexParam(2)
77 | if para_port:
78 | port = int(para_port)
79 | else:
80 | port = 8080
81 | msg_type = MNSSampleCommon.LoadIndexParam(3)
82 | if not msg_type:
83 | msg_type = u"XML"
84 | main(ip_addr, port, server.SimpleHttpNotifyEndpoint, msg_type)
85 |
--------------------------------------------------------------------------------
/sample/sample_common.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 | # Copyright (C) 2015, Alibaba Cloud Computing
4 |
5 | #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:
6 |
7 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | #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.
10 |
11 | import sys
12 | import os
13 | import time
14 | try:
15 | import configparser as ConfigParser
16 | except ImportError:
17 | import ConfigParser as ConfigParser
18 |
19 | class MNSSampleCommon:
20 |
21 | @staticmethod
22 | def LoadConfig():
23 | cfg_fn = os.path.join(os.path.dirname(os.path.abspath(__file__)) + "/../sample.cfg")
24 | required_ops = [("Base", "Endpoint")]
25 |
26 | parser = ConfigParser.ConfigParser()
27 | parser.read(cfg_fn)
28 | for sec,op in required_ops:
29 | if not parser.has_option(sec, op):
30 | sys.stderr.write("ERROR: need (%s, %s) in %s.\n" % (sec,op,cfg_fn))
31 | sys.stderr.write("Read README to get help inforamtion.\n")
32 | sys.exit(1)
33 |
34 | accessKeyId = os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
35 | accessKeySecret = os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
36 | securityToken = os.getenv("ALIBABA_CLOUD_ACCESS_SECURITY_TOKEN") or ""
37 | endpoint = parser.get("Base", "Endpoint")
38 |
39 | return accessKeyId,accessKeySecret,endpoint,securityToken
40 |
41 | @staticmethod
42 | def LoadParam(params_num):
43 | # The @topic_name is a bytes-stream on Python2, while it is a unicode-string on Python2.
44 | # So we must call @decode to decode bytes-stream to unicode-string when runing on Python2.
45 | # In addition, python3 has NOT @decode Attribute, so igonre the exception when runing on Python3.
46 | if params_num < len(sys.argv):
47 | params = list()
48 | hasdecode = hasattr(sys.argv[1], 'decode')
49 |
50 | for p in sys.argv[1:params_num+1]:
51 | if hasdecode:
52 | params.append(p.decode('utf-8'))
53 | else:
54 | params.append(p)
55 |
56 | return params_num, params
57 | else:
58 | return 0, None
59 |
60 | @staticmethod
61 | def LoadIndexParam(index):
62 | # The @topic_name is a bytes-stream on Python2, while it is a unicode-string on Python2.
63 | # So we must call @decode to decode bytes-stream to unicode-string when runing on Python2.
64 | # In addition, python3 has NOT @decode Attribute, so igonre the exception when runing on Python3.
65 | if index < len(sys.argv):
66 | if hasattr(sys.argv[1], 'decode'):
67 | return sys.argv[index].decode('utf-8')
68 | else:
69 | return sys.argv[index]
70 | else:
71 | return None
72 |
--------------------------------------------------------------------------------
/mns/mns_exception.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | class MNSExceptionBase(Exception):
11 | """
12 | @type type: string
13 | @param type: 错误类型
14 |
15 | @type message: string
16 | @param message: 错误描述
17 |
18 | @type req_id: string
19 | @param req_id: 请求的request_id
20 | """
21 | def __init__(self, type, message, req_id = None):
22 | self.type = type
23 | self.message = message
24 | self.req_id = req_id
25 |
26 | def get_info(self):
27 | if self.req_id is not None:
28 | return "(\"%s\" \"%s\") RequestID:%s\n" % (self.type, self.message, self.req_id)
29 | else:
30 | return "(\"%s\" \"%s\")\n" % (self.type, self.message)
31 |
32 | def __str__(self):
33 | return "MNSExceptionBase %s" % (self.get_info())
34 |
35 | class MNSClientException(MNSExceptionBase):
36 | def __init__(self, type, message, req_id = None):
37 | MNSExceptionBase.__init__(self, type, message, req_id)
38 |
39 | def __str__(self):
40 | return "MNSClientException %s" % (self.get_info())
41 |
42 | class MNSServerException(MNSExceptionBase):
43 | """ mns处理异常
44 |
45 | @note: 根据type进行分类处理,常见错误类型:
46 | : InvalidArgument 参数不合法
47 | : AccessDenied 无权对该资源进行当前操作
48 | : QueueNotExist 队列不存在
49 | : MessageNotExist 队列中没有消息
50 | : 更多错误类型请移步阿里云消息和通知服务官网进行了解;
51 | """
52 | def __init__(self, type, message, request_id, host_id, sub_errors=None):
53 | MNSExceptionBase.__init__(self, type, message, request_id)
54 | self.request_id = request_id
55 | self.host_id = host_id
56 | self.sub_errors = sub_errors
57 |
58 | def __str__(self):
59 | return "MNSServerException %s" % (self.get_info())
60 |
61 | class MNSClientNetworkException(MNSClientException):
62 | """ 网络异常
63 |
64 | @note: 检查endpoint是否正确、本机网络是否正常等;
65 | """
66 | def __init__(self, type, message, req_id=None):
67 | MNSClientException.__init__(self, type, message, req_id)
68 |
69 | def get_info(self):
70 | return "(\"%s\", \"%s\")\n" % (self.type, self.message)
71 |
72 | def __str__(self):
73 | return "MNSClientNetworkException %s" % (self.get_info())
74 |
75 | class MNSClientParameterException(MNSClientException):
76 | """ 参数格式错误
77 |
78 | @note: 请根据提示修改对应参数;
79 | """
80 | def __init__(self, type, message, req_id=None):
81 | MNSClientException.__init__(self, type, message, req_id)
82 |
83 | def __str__(self):
84 | return "MNSClientParameterException %s" % (self.get_info())
85 |
--------------------------------------------------------------------------------
/test/client/test_peek_message.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf-8
3 | import unittest
4 |
5 | from mns.mns_client import MNSClient
6 | from mns.mns_xml_handler import *
7 |
8 | RAW_XML_DATA = u"""
9 |
10 | 0909FF0D9C3A630C372B8A14BD2011B6
11 | 9D75198CAA17528AB23790C1861987AF
12 | I am 测试字符串.
13 | 8-0zxjHBaWtzPz8zphz0z6OGz5yWWidU1fJM
14 | 1716809414944
15 | 1716809420986
16 | 1716809470986
17 | 1
18 | 10
19 |
20 | """
21 | BASE64_XML_DATA = u"""
22 |
23 | 0909AC939C3A61357F898A13F0DBEB97
24 | A9B134C465C384F49EB89A6E3870D996
25 | SSBhbSDmtYvor5XlrZfnrKbkuLIu
26 | 8-0zxjHAX72zPzaztyz0z6GXzcSROgZnrPNl
27 | 1716809362651
28 | 1716809369536
29 | 1716809419536
30 | 1
31 | 10
32 |
33 | """
34 |
35 | access_id = "access_id"
36 | access_key = "access_key"
37 | host = "http://accoundId.mns.region.aliyuncs.com"
38 |
39 | class PeekMessageTest(unittest.TestCase):
40 |
41 | def test_peek_base64_message_not_decode(self):
42 | # 构建 request, 模拟 不进行base64解码 时, peek_message 对于 经过base64编码的消息体 的 decode()结果
43 | message_body = u"I am 测试字符串."
44 | queue_name = "test_queue"
45 | req = PeekMessageRequest(queue_name, False)
46 | resp = PeekMessageResponse()
47 | data = PeekMessageDecoder.decode(BASE64_XML_DATA, req.base64decode)
48 | MNSClient.make_peekresp(MNSClient(host, access_id, access_key), data, resp)
49 | # 不进行 base64解码时,peek_message 的 decode() 结果为 原base64编码字符串
50 | self.assertEqual(base64.b64encode(message_body.encode("utf-8")).decode("utf-8"), resp.message_body)
51 |
52 | def test_peek_base64_message_decode(self):
53 | # 构建 request, 模拟 进行base64解码 时, peek_message 对于 经过base64编码的消息体 的 decode()结果
54 | message_body = u"I am 测试字符串."
55 | queue_name = "test_queue"
56 | req = PeekMessageRequest(queue_name, True)
57 | resp = PeekMessageResponse()
58 | data = PeekMessageDecoder.decode(BASE64_XML_DATA, req.base64decode)
59 | MNSClient.make_peekresp(MNSClient(host, access_id, access_key), data, resp)
60 | # 进行 base64解码时,peek_message 的 decode() 结果为 base64解码后的字节串
61 | self.assertEqual(message_body.encode('utf-8'), resp.message_body)
62 |
63 | def test_peek_raw_message_not_decode(self):
64 | # 构建 request, 模拟 不进行base64解码 时,peek_message 对于 原始消息体 的 decode()结果
65 | message_body = u"I am 测试字符串."
66 | queue_name = "test_queue"
67 | req = PeekMessageRequest(queue_name, False)
68 | resp = PeekMessageResponse()
69 | data = PeekMessageDecoder.decode(RAW_XML_DATA, req.base64decode)
70 | MNSClient.make_peekresp(MNSClient(host, access_id, access_key), data, resp)
71 | # 不进行 base64解码时,peek_message 的 decode() 结果为 原始字符串
72 | self.assertEqual(message_body, resp.message_body)
73 |
74 | def test_peek_raw_message_decode(self):
75 | # 构建 request, 模拟 进行base64解码 时,peek_message 对于 原始消息体 的 decode()结果
76 | # 当 原字符串 中含有非 ascii 字符时, 发生 ValueError
77 | # 当 原字符串 不满足 base64 的填充时, 发生 binascii.Error, 其是 ValueError 的子类
78 | with self.assertRaises(ValueError):
79 | message_body = u"I am 测试字符串."
80 | queue_name = "test_queue"
81 | req = PeekMessageRequest(queue_name, True)
82 | resp = PeekMessageResponse()
83 | # 解码时会发生错误
84 | data = PeekMessageDecoder.decode(RAW_XML_DATA, req.base64decode)
85 | MNSClient.make_peekresp(MNSClient(host, access_id, access_key), data, resp)
86 | # 不进行 base64解码时,peek_message 的 decode() 结果为 原始字符串
87 | self.assertEqual(message_body, resp.message_body)
88 |
89 |
90 | if __name__ == '__main__':
91 | unittest.main()
92 |
--------------------------------------------------------------------------------
/test/client/test_receive_message.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf-8
3 | import unittest
4 |
5 | from mns.mns_xml_handler import *
6 | from mns.queue import *
7 |
8 | RAW_XML_DATA = u"""
9 |
10 | 0909FF0D9C3A630C372B8A14BD2011B6
11 | 9D75198CAA17528AB23790C1861987AF
12 | I am 测试字符串.
13 | 8-0zxjHBaWtzPz8zphz0z6OGz5yWWidU1fJM
14 | 1716809414944
15 | 1716809420986
16 | 1716809470986
17 | 1
18 | 10
19 |
20 | """
21 | BASE64_XML_DATA = u"""
22 |
23 | 0909AC939C3A61357F898A13F0DBEB97
24 | A9B134C465C384F49EB89A6E3870D996
25 | SSBhbSDmtYvor5XlrZfnrKbkuLIu
26 | 8-0zxjHAX72zPzaztyz0z6GXzcSROgZnrPNl
27 | 1716809362651
28 | 1716809369536
29 | 1716809419536
30 | 1
31 | 10
32 |
33 | """
34 |
35 | access_id = "access_id"
36 | access_key = "access_key"
37 | host = "http://accoundId.mns.region.aliyuncs.com"
38 |
39 | class ReceiveMessageTest(unittest.TestCase):
40 |
41 | def test_receive_base64_message_not_decode(self):
42 | # 构建 request, 模拟 不进行base64解码 时, receive_message 对于 经过base64编码的消息体 的 decode()结果
43 | message_body = u"I am 测试字符串."
44 | queue_name = "test_queue"
45 | req = ReceiveMessageRequest(queue_name, False)
46 | resp = ReceiveMessageResponse()
47 | data = RecvMessageDecoder.decode(BASE64_XML_DATA, req.base64decode)
48 | MNSClient.make_recvresp(MNSClient(host, access_id, access_key), data, resp)
49 | # 不进行 base64解码时,receive_message 的 decode() 结果为 原base64编码字符串
50 | self.assertEqual(base64.b64encode(message_body.encode("utf-8")).decode("utf-8"), resp.message_body)
51 |
52 | def test_receive_base64_message_decode(self):
53 | # 构建 request, 模拟 进行base64解码 时, receive_message 对于 经过base64编码的消息体 的 decode()结果
54 | message_body = u"I am 测试字符串."
55 | queue_name = "test_queue"
56 | req = ReceiveMessageRequest(queue_name, True)
57 | resp = ReceiveMessageResponse()
58 | data = RecvMessageDecoder.decode(BASE64_XML_DATA, req.base64decode)
59 | MNSClient.make_recvresp(MNSClient(host, access_id, access_key), data, resp)
60 | # 进行 base64解码时,receive_message 的 decode() 结果为 base64解码后的字节串
61 | self.assertEqual(message_body.encode('utf-8'), resp.message_body)
62 |
63 | def test_receive_raw_message_not_decode(self):
64 | # 构建 request, 模拟 不进行base64解码 时,receive_message 对于 原始消息体 的 decode()结果
65 | message_body = u"I am 测试字符串."
66 | queue_name = "test_queue"
67 | req = ReceiveMessageRequest(queue_name, False)
68 | resp = ReceiveMessageResponse()
69 | data = RecvMessageDecoder.decode(RAW_XML_DATA, req.base64decode)
70 | MNSClient.make_recvresp(MNSClient(host, access_id, access_key), data, resp)
71 | # 不进行 base64解码时,receive_message 的 decode() 结果为 原始字符串
72 | self.assertEqual(message_body, resp.message_body)
73 |
74 | def test_receive_raw_message_decode(self):
75 | # 构建 request, 模拟 进行base64解码 时,receive_message 对于 原始消息体 的 decode()结果
76 | # 当 原字符串 中含有非 ascii 字符时, 发生 ValueError
77 | # 当 原字符串 不满足 base64 的填充时, 发生 binascii.Error, 其是 ValueError 的子类
78 | with self.assertRaises(ValueError):
79 | message_body = u"I am 测试字符串."
80 | queue_name = "test_queue"
81 | req = ReceiveMessageRequest(queue_name, True)
82 | resp = ReceiveMessageResponse()
83 | # 解码时会发生错误
84 | data = RecvMessageDecoder.decode(RAW_XML_DATA, req.base64decode)
85 | MNSClient.make_recvresp(MNSClient(host, access_id, access_key), data, resp)
86 | # 不进行 base64解码时,peek_message 的 decode() 结果为 原始字符串
87 | self.assertEqual(message_body, resp.message_body)
88 |
89 |
90 | if __name__ == '__main__':
91 | unittest.main()
--------------------------------------------------------------------------------
/sample/simple_https_notify_endpoint.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 | # Copyright (C) 2015, Alibaba Cloud Computing
4 |
5 | #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:
6 |
7 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | #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.
10 |
11 | import sys
12 | import cgi
13 | import shutil
14 | import socket
15 | import base64
16 | import logging
17 | import ssl
18 | try:
19 | from urllib.request import urlopen
20 | except ImportError:
21 | from urllib2 import urlopen
22 | except Exception as err:
23 | raise(err)
24 |
25 | import logging.handlers
26 | import xml.dom.minidom
27 | #import BaseHTTPServer
28 | try:
29 | from http.server import BaseHTTPRequestHandler, HTTPServer
30 | except ImportError:
31 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
32 | except Exception as err:
33 | raise(err)
34 |
35 | #import SocketServer
36 | try:
37 | import socketserver as SocketServer
38 | except ImportError:
39 | import SocketServer as SocketServer
40 |
41 | from sample_common import MNSSampleCommon
42 | import server
43 |
44 | __version__ = "1.0.3"
45 | _LOGGER = logging.getLogger(__name__)
46 |
47 |
48 | RSA_PRIVATE_KEY = u"\n-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQC7UKbXif9YkFQAghYs0CEZL066Sy2YbEKHdVm1OqmIHHY9CV9s\nFeCMD9HbbDwsBA2XQsVb7NP5MRydwTCpwCOBpntr0w94PuE+Q8TcLSHxgoqqI29s\nzF0xyRXjAJFabzu2sei8RySLw57C64lWIOxPrWsi+GHQK0XcFU7JfFACIQIDAQAB\nAoGBAKtDfZia2vYN2FAyoLXOgkS1pWTdsc2oRlf16tSx0ynY5B7AgBeiFRHasQTP\nfGC+P/LqIOsAqXsw9Toj1iuOuqaSBYpuCHFMe/dxrEAPXXA7GCMwW3lDeSfMinHV\nrjLTDMhZRLN+jT5QvlkOBNibaZSc3bmCwmGbkEeREkDGD+fFAkEA6A/pQQfT25ip\nAKHh2VOIzfOpiBfC0sci6ZF845kh5GxyFUAeq+hjUUx+ihzI7eIqKrkY+41eYz73\nSGhwuBkmhwJBAM6jGWNv0PgxcLs+sGa55BL53KuiVjIONxOhKrk3OfoF+i8jY7c7\ngUE3kgckcx7FZY323kjGSwX626+jvyRdFBcCQDv9kQEcsun75wSg1K/H5n/HU7Y4\n3kZ68E2NLMnxlk9ksYFI2CT8qGAl9DhkBJVqeBgfTZQKEbJ6Xpa7WRheeBUCQQDE\nS5oFpSYdcFIH/lBy9aodALFJdqhtWqWlhxff5P+1bNIyz2qdmPB7tL+K+2xE0f5c\nMyUMexqv7pOdMW+Vqro3AkAVx3pYn7e1YqMM40jI0J+CqyhXYZ2esiWvBylS+FUN\nY6WOonIAv774LIURaTplcAMuOAj6VDHpVmSDVvnVMgEu\n-----END RSA PRIVATE KEY-----"
49 |
50 | CERTIFICATE = u"\n-----BEGIN CERTIFICATE-----\nMIIDbDCCAtWgAwIBAgIJALKoPicL21iaMA0GCSqGSIb3DQEBBQUAMIGBMQswCQYD\nVQQGEwJDTjERMA8GA1UECBMIWmhlamlhbmcxETAPBgNVBAcTCEhhbmd6aG91MQ8w\nDQYDVQQKDAZBbGluCAgxEzARBgNVBAsTCkFwc2FyYSBPU1MxDDAKBgNVBAMTA09T\nUzEYMBYGCSqGSIb3DQEJARYJYWxleC5rcQgIMB4XDTE0MDgyMDA4MjM1NVoXDTE1\nMDgyMDA4MjM1NVowgYExCzAJBgNVBAYTAkNOMREwDwYDVQQIEwhaaGVqaWFuZzER\nMA8GA1UEBxMISGFuZ3pob3UxDzANBgNVBAoMBkFsaW4ICDETMBEGA1UECxMKQXBz\nYXJhIE9TUzEMMAoGA1UEAxMDT1NTMRgwFgYJKoZIhvcNAQkBFglhbGV4LmtxCAgw\ngZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALtQpteJ/1iQVACCFizQIRkvTrpL\nLZhsQod1WbU6qYgcdj0JX2wV4IwP0dtsPCwEDZdCxVvs0/kxHJ3BMKnAI4Gme2vT\nD3g+4T5DxNwtIfGCiqojb2zMXTHJFeMAkVpvO7ax6LxHJIvDnsLriVYg7E+tayL4\nYdArRdwVTsl8UAIhAgMBAAGjgekwgeYwHQYDVR0OBBYEFMuRh/onWCJ+geGxBp6Y\nMEugx/0HMIG2BgNVHSMEga4wgauAFMuRh/onWCJ+geGxBp6YMEugx/0HoYGHpIGE\nMIGBMQswCQYDVQQGEwJDTjERMA8GA1UECBMIWmhlamlhbmcxETAPBgNVBAcTCEhh\nbmd6aG91MQ8wDQYDVQQKDAZBbGluCAgxEzARBgNVBAsTCkFwc2FyYSBPU1MxDDAK\nBgNVBAMTA09TUzEYMBYGCSqGSIb3DQEJARYJYWxleC5rcQgIggkAsqg+JwvbWJow\nDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQA/8bbaN0Zwb44belQ+OaWj\n7xgn1Bp7AbkDnybpCB1xZGE5sDSkoy+5lNW3D/G5cEQkMYc8g18JtEOy0PPMKHvN\nmqxXUOCSGTmiqOxSY0kZwHG5sMv6Tf0KOmBZte3Ob2h/+pzNMHOBTFFd0xExKGlr\nGr788nh1/5YblcBHl3VEBA==\n-----END CERTIFICATE-----"
51 |
52 |
53 | def main(ip_addr, port, endpoint_class = server.SimpleHttpNotifyEndpoint, msg_type=u"XML", prefix=u"https://"):
54 | #init logger
55 | global logger
56 | endpoint_class.access_log_file = "access_log.%s" % port
57 | endpoint_class.msg_type = msg_type
58 | log_file = "endpoint_log.%s" % port
59 | logger = logging.getLogger()
60 | file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100*1024*1024)
61 | formatter = logging.Formatter('[%(asctime)s] [%(name)s] [%(levelname)s] [%(filename)s:%(lineno)d] [%(thread)d] %(message)s', '%Y-%m-%d %H:%M:%S')
62 | file_handler.setFormatter(formatter)
63 | logger.addHandler(file_handler)
64 | logger.setLevel(logging.INFO)
65 |
66 | tmpcertfile = "x509_public_certificate_checkhttp.pem"
67 | tmpkeyfile = "rsa_private_key_checkhttp.pem"
68 | open(tmpcertfile, 'w').write(CERTIFICATE)
69 | open(tmpkeyfile, 'w').write(RSA_PRIVATE_KEY)
70 |
71 | #start endpoint
72 | addr_info = "Start Endpoint! Address: %s%s:%s" % (prefix, ip_addr, port)
73 | print(addr_info)
74 | try:
75 | logger.info(addr_info)
76 | httpd = HTTPServer((ip_addr, port), endpoint_class)
77 | httpd.socket = ssl.wrap_socket(httpd.socket, keyfile=tmpkeyfile, certfile=tmpcertfile, server_side=True)
78 | httpd.serve_forever()
79 | #httpd = server.ThreadedHTTPServer(('', port), endpoint_class)
80 | except KeyboardInterrupt:
81 | print("Shutting down the simple notify endpoint!")
82 | httpd.socket.close()
83 |
84 | if __name__ == "__main__":
85 |
86 | ip_addr = MNSSampleCommon.LoadIndexParam(1)
87 | if not ip_addr:
88 | print("ERROR: Must specify IP address")
89 | sys.exit(0)
90 |
91 | para_port = MNSSampleCommon.LoadIndexParam(2)
92 | if para_port:
93 | port = int(para_port)
94 | else:
95 | port = 8080
96 | msg_type = MNSSampleCommon.LoadIndexParam(3)
97 | if not msg_type:
98 | msg_type = u"XML"
99 |
100 | main(ip_addr, port, server.SimpleHttpNotifyEndpoint, msg_type)
101 |
--------------------------------------------------------------------------------
/mns/mns_http.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 |
11 | import time
12 | import socket
13 | try:
14 | from http.client import HTTPConnection, BadStatusLine, HTTPSConnection
15 | except:
16 | from httplib import HTTPConnection, BadStatusLine, HTTPSConnection
17 | from .mns_exception import *
18 |
19 | class MNSHTTPConnection(HTTPConnection):
20 | def __init__(self, host, port=None, strict=None, connection_timeout=60):
21 | HTTPConnection.__init__(self, host, port)
22 | self.request_length = 0
23 | self.connection_timeout = connection_timeout
24 |
25 | def send(self, str):
26 | HTTPConnection.send(self, str)
27 | self.request_length += len(str)
28 |
29 | def request(self, method, url, body=None, headers={}):
30 | self.request_length = 0
31 | HTTPConnection.request(self, method, url, body, headers)
32 |
33 | def connect(self):
34 | msg = "getaddrinfo returns an empty list"
35 | for res in socket.getaddrinfo(self.host, self.port, 0,
36 | socket.SOCK_STREAM):
37 | af, socktype, proto, canonname, sa = res
38 | try:
39 | self.sock = socket.socket(af, socktype, proto)
40 | self.sock.settimeout(self.connection_timeout)
41 | if self.debuglevel > 0:
42 | print("connect: (%s, %s)" % (self.host, self.port))
43 | self.sock.connect(sa)
44 | except socket.error as e:
45 | msg = e
46 | if self.debuglevel > 0:
47 | print('connect fail:', (self.host, self.port))
48 | if self.sock:
49 | self.sock.close()
50 | self.sock = None
51 | continue
52 | break
53 | if not self.sock:
54 | raise socket.error(msg)
55 |
56 | class MNSHTTPSConnection(HTTPSConnection):
57 | def __init__(self, host, port=None, strict=None):
58 | HTTPSConnection.__init__(self, host, port)
59 | self.request_length = 0
60 |
61 | def send(self, str):
62 | HTTPSConnection.send(self, str)
63 | self.request_length += len(str)
64 |
65 | def request(self, method, url, body=None, headers={}):
66 | self.request_length = 0
67 | HTTPSConnection.request(self, method, url, body, headers)
68 |
69 | class MNSHttp:
70 | def __init__(self, host, connection_timeout = 60, keep_alive = True, logger=None, is_https=False):
71 | if is_https:
72 | self.conn = MNSHTTPSConnection(host)
73 | else:
74 | self.conn = MNSHTTPConnection(host, connection_timeout=connection_timeout)
75 | self.host = host
76 | self.is_https = is_https
77 | self.connection_timeout = connection_timeout
78 | self.keep_alive = keep_alive
79 | self.request_size = 0
80 | self.response_size = 0
81 | self.logger = logger
82 | if self.logger:
83 | self.logger.info("InitMNSHttp KeepAlive:%s ConnectionTime:%s" % (self.keep_alive, self.connection_timeout))
84 |
85 | def set_log_level(self, log_level):
86 | if self.logger:
87 | self.logger.setLevel(log_level)
88 |
89 | def close_log(self):
90 | self.logger = None
91 |
92 | def set_connection_timeout(self, connection_timeout):
93 | self.connection_timeout = connection_timeout
94 | if not self.is_https:
95 | if self.conn:
96 | self.conn.close()
97 | self.conn = MNSHTTPConnection(self.host, connection_timeout=connection_timeout)
98 |
99 | def set_keep_alive(self, keep_alive):
100 | self.keep_alive = keep_alive
101 |
102 | def is_keep_alive(self):
103 | return self.keep_alive
104 |
105 | def send_request(self, req_inter):
106 | try:
107 | if self.logger:
108 | self.logger.debug("SendRequest %s" % req_inter)
109 | self.conn.request(req_inter.method, req_inter.uri, req_inter.data, req_inter.header)
110 | self.conn.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
111 | try:
112 | http_resp = self.conn.getresponse()
113 | except BadStatusLine:
114 | #open another connection when keep-alive timeout
115 | #httplib will not handle keep-alive timeout, so we must handle it ourself
116 | self.conn.close()
117 | self.conn.request(req_inter.method, req_inter.uri, req_inter.data, req_inter.header)
118 | self.conn.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
119 | http_resp = self.conn.getresponse()
120 | headers = dict(http_resp.getheaders())
121 | resp_inter = ResponseInternal(status = http_resp.status, header = headers, data = http_resp.read())
122 | resp_inter.data = resp_inter.data.decode('utf-8')
123 | self.request_size = self.conn.request_length
124 | self.response_size = len(resp_inter.data)
125 | if not self.is_keep_alive():
126 | self.conn.close()
127 | if self.logger:
128 | self.logger.debug("GetResponse %s" % resp_inter)
129 | return resp_inter
130 | except Exception as e:
131 | self.conn.close()
132 | raise MNSClientNetworkException("NetWorkException", str(e), req_inter.get_req_id()) #raise netException
133 |
134 | class RequestInternal:
135 | def __init__(self, method = "", uri = "", header = None, data = ""):
136 | if header == None:
137 | header = {}
138 | self.method = method
139 | self.uri = uri
140 | self.header = header
141 | self.data = data
142 |
143 | def get_req_id(self):
144 | return self.header.get("x-mns-user-request-id")
145 |
146 | def __str__(self):
147 | return "Method: %s\nUri: %s\nHeader: %s\nData: %s\n" % \
148 | (self.method, self.uri, "\n".join(["%s: %s" % (k,v) for k,v in self.header.items()]), self.data)
149 |
150 | class ResponseInternal:
151 | def __init__(self, status = 0, header = None, data = ""):
152 | if header == None:
153 | header = {}
154 | self.status = status
155 | self.header = header
156 | self.data = data
157 |
158 | def __str__(self):
159 | return "Status: %s\nHeader: %s\nData: %s\n" % \
160 | (self.status, "\n".join(["%s: %s" % (k,v) for k,v in self.header.items()]), self.data)
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Aliyun MNS Python SDK
2 |
3 | [](https://badgen.net/badge/color/1.2.0/green?label=version)
4 |
5 | # 关于
6 |
7 | - 此 Python SDK 基于 [阿里云消息服务 MNS](https://www.aliyun.com/product/mns/) 官方 API 构建。
8 | - 阿里云消息服务(Message Service,简称 MNS)是一种高效、可靠、安全、便捷、可弹性扩展的分布式消息服务。
9 | - MNS 能够帮助应用开发者在他们应用的分布式组件上自由的传递数据、通知消息,构建松耦合系统。
10 | - 使用此 SDK,用户可以快速构建高可靠、高并发的一对一消费模型和一对多的发布订阅模型。
11 |
12 | # 简介
13 |
14 | - 这篇文档主要介绍如何使用 Python 来进行 Message Service API 调用,并且介绍 mnscmd 的简单使用方法。
15 | - 这篇文档假设您已经熟悉 Python,熟悉 Message Service 的相关概念,并且已经注册阿里云账号、开通阿里云的消息服务,且获得了相应的
16 | AccessKeyId、
17 | AccessKeySecret 和 AccountId。
18 | - 如果您还没有开通或者还不了解 Message Service,请移步 [阿里云消息服务官方网站](https://www.aliyun.com/product/mns/)。
19 |
20 | # 环境要求
21 |
22 | Python SDK 需要:安装 Python 2.5 及以上的版本。
23 | 可以在 Windows 平台和 Linux 平台使用。
24 |
25 | # 安装方法
26 |
27 | ## pip 安装依赖
28 |
29 | ```pip install aliyun-mns-sdk```
30 |
31 | ## 下载 SDK 的方式安装
32 |
33 | 下载 SDK 并解压,安装 SDK 和 mnscmd
34 |
35 | 1. linux 平台
36 | ```sudo python setup.py install```
37 |
38 | 2. Windows 平台
39 | ```python.exe setup.py install```
40 |
41 | # 快速使用
42 |
43 | ## SDK
44 |
45 | > 注意事项:Account, Queue, Topic, Subscription 均不是线程安全的,多线程场景下请独立构造对象。
46 |
47 | ### 运行 sample.py
48 |
49 | 1. 下载最新版 Python SDK,进入 SDK 根目录。
50 | 2. 修改 sample.cfg 文件 Endpoint 为您自己的接入点,可登录 [MNS 控制台](https://mns.console.aliyun.com/) 查看,
51 | 具体操作,请参考 [获取接入点](https://help.aliyun.com/zh/mns/user-guide/manage-queues-in-the-console?spm=a2c4g.11186623.0.i25#section-yhc-ix5-300)。
52 | 3. 在环境变量中设置您的 `ALIBABA_CLOUD_ACCESS_KEY_ID` 和 `ALIBABA_CLOUD_ACCESS_KEY_SECRET`
53 | ,阿里云身份验证信息在 [RAM 控制台](https://ram.console.aliyun.com/)
54 | 创建。获取方式请参考 [获取 AccessKey](https://help.aliyun.com/document_detail/53045.html?spm=a2c4g.11186623.0.i29#task-354412)。
55 | 4. 根据阿里云规范,您应该将 AK/SK
56 | 信息设置为环境变量使用,请参考 [设置环境变量](https://help.aliyun.com/zh/sdk/developer-reference/configure-the-alibaba-cloud-accesskey-environment-variable-on-linux-macos-and-windows-systems)。
57 | 5. linux 平台运行 `python sample.py`,Windows 平台运行 `python.exe sample.py`。
58 |
59 | ### 运行 simple_notify_endpoint.py
60 |
61 | 1. 下载最新版 Python SDK,进入 sample 目录。
62 | 2. 下载并安装 [pycryptodome](https://pypi.org/project/pycryptodome/)。
63 | 3. linux 平台 `python simple_http_notify_endpoint.py 127.0.0.1 [port]`,
64 | Windows 平台运行 `python.exe simple_http_notify_endpoint.py 127.0.0.1 [port]`,
65 | 端口号默认为 8080。
66 | 4. 启动 simple_http_notify_endpoint.py 后会输出监听的地址:`http://127.0.0.1:port`。
67 | 5. 接收消息需要 Endpoint 公网可达,将该地址的 127.0.0.1 换为节点的公网 ip 后作为 Subscription 的 Endpoint 属性即可接收推送到该
68 | Subscription 的消息。
69 |
70 | ### 单元测试
71 |
72 | 1. 下载最新版 Python SDK,进入 SDK 根目录。
73 | 2. 运行 ```python -m unittest discover -s test``` 即可运行所有测试。
74 |
75 | ## mnscmd
76 |
77 | > 注意:在 Windows 平台 cmd 中 mnscmd 不能直接运行,需要进入 bin 目录,用 `python.exe mnscmd` 替换使用帮助中的 `mnscmd`。
78 | > 在 SDK 主目录下,修改了 mnscmd 逻辑后,请使用修改后的 mnscmd 路径替换使用帮助中的 `mnscmd`,如 `bin/mnscmd YOUR_COMMAND`。
79 |
80 | ### 配置
81 |
82 | 配置访问 MNS 所需要的认证信息
83 | 命令:```mnscmd config --mnsendpoint=YOUR_ENDPOINT --accesskeyid=YOUR_ACCESSKEYID --accesskeysecret=YOUR_ACCESSKEYSECRET```
84 |
85 | ### Account 相关命令
86 |
87 | 1. 获取 Account 的属性
88 | 命令:`mnscmd getaccountattr`
89 |
90 | ### Queue 相关命令
91 |
92 | 1. 列出创建的 queue:
93 | - 命令:`mnscmd listqueue`
94 | - 如果是刚刚使用 MNS 的用户因为没有创建 queue,输出是空
95 |
96 | 2. 创建 queue:
97 | - 命令:`mnscmd createqueue --queuename=myqueue`
98 | - 帮助:`mnscmd createqueue --info`
99 | - "myqueue" 可以根据需求修改为符合规则的 queue name, queue name 的详细规则请移步阿里云消息服务官方网站
100 | - 更多属性指定,运行帮助命令
101 |
102 | 3. 获取 queue:
103 | - 命令:`mnscmd getqueueattr --queuename=myqueue`
104 | - 命令返回 queue 的各项属性
105 |
106 | 4. 设置 queue 属性:
107 | - 命令:`mnscmd setqueueattr --queuename=myqueue --delaysec=5`
108 | - 帮助:`mnscmd setqueueattr --info`
109 | - 设置 queue 的 delayseconds 为 5 秒
110 | - 更多属性设置,运行帮助命令
111 |
112 | 5. 发送 message:
113 | - 命令:`mnscmd sendmessage --queuename=myqueue --body="I am a test message."`
114 | - 帮助:`mnscmd sendmessage --info`
115 | - 发送一条消息到队列 myqueue 中
116 | - 更多属性指定,运行帮助命令
117 |
118 | 6. 查看 message:
119 | - 命令:`mnscmd peekmessage --queuename=myqueue`
120 | - 查看 myqueue 中的第一条消息
121 |
122 | 7. 消费 message:
123 | - 命令:`mnscmd receivemessage --queuename=myqueue`
124 | - 消费 myqueue 中的第一条消息
125 | - 命令返回消息基本信息和临时句柄 (ReceiptHandle)
126 |
127 | 8. 修改 message 下次可消费时间:
128 | - 命令:`mnscmd changevisibility --queuename=myqueue --handle=YOUR_RECEIPTHANDLE --vistimeout=10`
129 | - YOUR_RECEIPTHANDLE 是 receivemessage 返回的 ReceiptHandle
130 | - 消息 10 秒后可再次被消费,命令返回新的 ReceiptHandle
131 |
132 | 9. 删除 message:
133 | - 命令:`mnscmd deletemessage --queuename=myqueue --handle=YOUR_RECEIPTHANDLE`
134 | - YOUR_RECEIPTHANDLE 是最近一次操作返回的 ReceiptHandle,即第 9 步返回的 ReceiptHandle
135 |
136 | 10. 删除 queue:
137 | - 命令:`mnscmd deletequeue --queuename=myqueue`
138 | - 注意,如果 queue 中有 message,所有 message 都会被删除
139 |
140 | ### Topic 相关命令
141 |
142 | 1. 列出创建的 topic:
143 | - 命令:`mnscmd listtopic`
144 | - 帮助:`mnscmd listtopic --info`
145 | - 命令返回 topic 的 URL 列表,--prefix 指定 topic 名称的前缀,--retnum 指定返回的 topic 个数,--marker 指定 topic 的起始位置
146 |
147 | 2. 创建 topic:
148 | - 命令:`mnscmd createtopic --topicname=mytopic`
149 | - 帮助:`mnscmd createtopic --info`
150 | - 创建名称为 "my topic" 的主题,"my topic" 可以根据需要修改为符合规则的 topic name,topic name 的详细规则请移步阿里云消息服务官方网站
151 |
152 | 3. 获取 topic 属性:
153 | - 命令:`mnscmd gettopicattr --topicname=mytopic`
154 | - 帮助:`mnscmd gettopicattr --info`
155 | - 命令获取 topic 的各项属性
156 |
157 | 4. 设置 topic 属性:
158 | - 命令:`mnscmd settopicattr --topicname=mytopic --maxmsgsize=1024`
159 | - 帮助:`mnscmd settopicattr --info`
160 | - 设置 topic 的最大消息长度 1024 Byte
161 |
162 | 5. 发布消息:
163 | - 命令:`mnscmd publishmessage --topicname=mytopic --body="I am a test message."`
164 | - 帮助:`mnscmd publishmessage --info`
165 | - 发送一条消息到主题 mytopic 中
166 |
167 | 6. 列出 topic 的 subscription:
168 | - 命令:`mnscmd listsub --topicname=mytopic`
169 | - 帮助:`mnscmd listsub --info`
170 | - 命令返回订阅 mytopic 的 subscription URL 列表,--prefix 指定 subscription 名称的前缀,--retnum 指定返回的
171 | subscription 个数,--marker 指定起始位置
172 |
173 | 7. 创建 subscription:
174 | - 命令:`mnscmd subscribe --topicname=mytopic --subname=mysub --endpoint=http://test-endpoint`
175 | - 帮助:`mnscmd subscribe --info`
176 | - 创建一个名叫 mysub 的 subscription,订阅 mytopic,指定 endpoint 为:http://test-endpoint
177 |
178 | 8. 获取 subscription 属性:
179 | - 命令:`mnscmd getsubattr --topicname=mytopic --subname=mysub`
180 | - 帮助:`mnscmd getsubattr --info`
181 | - 获取 mysub 的各项属性
182 |
183 | 9. 设置 subscription 属性:
184 | - 命令:`mnscmd setsubattr --topicname=mytopic --subname=mysub --notifystrategy=BACKOFF_RETRY`
185 | - 帮助:`mnscmd setsubattr --info`
186 | - 设置 mysub 的重传策略为 BACKOFF_RETRY
187 |
188 | 10. 删除 subscription:
189 | - 命令:`mnscmd unsubscribe --topicname=mytopic --subname=mysub`
190 | - 帮助:`mnscmd unsubscribe --info`
191 | - 删除 mysub
192 |
193 | 11. 删除 topic:
194 | - 命令:`mnscmd deletetopic --topicname=mytopic`
195 | - 帮助:`mnscmd deletetopic --info`
196 | - 删除 mytopic,注意:该操作会删除 mytopic 的所有消息和订阅该 topic 的 subscription
197 | - 帮助:`mnscmd deletetopic --info`
198 |
199 | # ChangeHistory
200 |
201 | ## 1.2.0 - 2024-07-26
202 |
203 | * 支持 Topic/Queue 模型 生产和消费,base 64 编解码可选
204 | * 新增 receive_message_with_str_body 等方法,支持接收消息的消息体是字符串类型。
205 | * 支持 [阿里云规范(AK/SK)](https://help.aliyun.com/zh/sdk/developer-reference/configure-the-alibaba-cloud-accesskey-environment-variable-on-linux-macos-and-windows-systems) 基于 env 获取
206 | * 修复 peek_message 时队列的 base64 编解码设置失效,始终进行 base64 解码的问题
207 | * 修复 python 版本兼容性问题
208 | * 规范客户端版本上报规范
209 |
210 | ## 1.1.6 - 2020-11-19
211 |
212 | * 增加发送消息返回值的 ReceiptHandle 解析
213 | * 修复 Python 3 兼容性问题
214 |
215 | ## 1.1.5 - 2019-04-26
216 |
217 | * 兼容 Python 3 版本。
218 |
219 | ## 1.1.4 - 2017-03-14
220 |
221 | * 主题模型支持短信推送
222 | * 队列 / 主题支持消息包含中文
223 | * mnscmd 支持参数指定 host、accesskey 对
224 | * mnscmd 支持指定是否对队列消息做 base64 编码和解码
225 |
226 | ## 1.1.3 - 2016-09-13
227 |
228 | * 支持透传 RequestID 到 MNS 端
229 | * Topic 推送支持 QueueEndpoint 和 MailEndpoint
230 | * 主题消息推送支持 json 格式
231 | * mnscmd 支持 --config_file 指定配置文件
232 |
233 | ## 1.1.2 - 2016-04-25
234 |
235 | * Topic 推送支持消息过滤
236 | * 增加 sample 目录,包含更详细的示例代码
237 |
238 | ## 1.1.1 - 2016-03-25
239 |
240 | * 支持 Https 访问
241 | * Queue 和 Topic 支持 LoggingEnabled 属性设置和查询
242 | * 支持设置和获取 Account 的属性
243 |
244 | ## 1.1.0 - 2016-01-05
245 |
246 | * 支持 Topic 相关接口
247 | * 提供 simple_http_notify_endpoint.py 和 simple_https_notify_endpoint.py
248 | * 支持 STS 访问
249 |
250 | ## 1.0.2 - 2015-06-01
251 |
252 | * 支持 SDK 安装
253 | * 提供 mnscmd 命令
254 |
255 | ## 1.0.1 - 2015-02-03
256 |
257 | * 统一队列非字符串属性为 int 类型;
258 | * 修正 SetQueueAttr 的 http 状态码为 204。
259 |
260 | ## 1.0.0 - 2014-08-01
261 |
262 | * SDK 支持 queue 的创建、修改、获取、删除,message 的发送、查看、消费、删除和修改下次可消费时间。
--------------------------------------------------------------------------------
/mns/subscription.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | import time
11 | from .mns_client import MNSClient
12 | from .mns_request import *
13 | from .mns_exception import *
14 |
15 | class Subscription:
16 | def __init__(self, topic_name, subscription_name, mns_client, debug=False):
17 | self.topic_name = topic_name
18 | self.subscription_name = subscription_name
19 | self.mns_client = mns_client
20 | self.debug = debug
21 |
22 | def set_debug(self, debug):
23 | self.debug = debug
24 |
25 | def subscribe(self, subscription_meta, req_info=None):
26 | """ 创建订阅
27 |
28 | @type subscription_meta: SubscriptionMeta object
29 | @param subscription_meta: SubscriptionMeta对象,指定订阅的属性
30 |
31 | @type req_info: RequestInfo object
32 | @param req_info: 透传到MNS的请求信息
33 |
34 | @rtype: string
35 | @return 新创建订阅的URL
36 |
37 | @note: Exception
38 | :: MNSClientParameterException 参数格式异常
39 | :: MNSClientNetworkException 网络异常
40 | :: MNSServerException mns处理异常
41 | """
42 | req = SubscribeRequest(self.topic_name,
43 | self.subscription_name,
44 | subscription_meta.endpoint,
45 | subscription_meta.notify_strategy,
46 | subscription_meta.notify_content_format,
47 | subscription_meta.filter_tag)
48 | req.set_req_info(req_info)
49 | resp = SubscribeResponse()
50 | self.mns_client.subscribe(req, resp)
51 | self.debuginfo(resp)
52 | return resp.subscription_url
53 |
54 | def get_attributes(self, req_info=None):
55 | """ 获取订阅属性
56 |
57 | @type req_info: RequestInfo object
58 | @param req_info: 透传到MNS的请求信息
59 |
60 | @rtype: SubscriptionMeta object
61 | @return 订阅的属性
62 |
63 | @note: Exception
64 | :: MNSClientNetworkException 网络异常
65 | :: MNSServerException mns处理异常
66 | """
67 | req = GetSubscriptionAttributesRequest(self.topic_name, self.subscription_name)
68 | req.set_req_info(req_info)
69 | resp = GetSubscriptionAttributesResponse()
70 | self.mns_client.get_subscription_attributes(req, resp)
71 | subscription_meta = SubscriptionMeta()
72 | self.__resp2meta__(subscription_meta, resp)
73 | self.debuginfo(resp)
74 | return subscription_meta
75 |
76 | def set_attributes(self, subscription_meta, req_info=None):
77 | """ 设置订阅的属性
78 |
79 | @type subscription_meta: SubscriptionMeta object
80 | @param subscription_meta: 新设置的订阅属性
81 |
82 | @type req_info: RequestInfo object
83 | @param req_info: 透传到MNS的请求信息
84 |
85 | @note: Exception
86 | :: MNSClientParameterException 参数格式异常
87 | :: MNSClientNetworkException 网络异常
88 | :: MNSServerException mns处理异常
89 | """
90 | req = SetSubscriptionAttributesRequest(self.topic_name,
91 | self.subscription_name,
92 | subscription_meta.endpoint,
93 | subscription_meta.notify_strategy)
94 | req.set_req_info(req_info)
95 | resp = SetSubscriptionAttributesResponse()
96 | self.mns_client.set_subscription_attributes(req, resp)
97 | self.debuginfo(resp)
98 |
99 | def unsubscribe(self, req_info=None):
100 | """ 删除订阅
101 |
102 | @type req_info: RequestInfo object
103 | @param req_info: 透传到MNS的请求信息
104 |
105 | @note: Exception
106 | :: MNSClientNetworkException 网络异常
107 | :: MNSServerException mns处理异常
108 | """
109 | req = UnsubscribeRequest(self.topic_name, self.subscription_name)
110 | req.set_req_info(req_info)
111 | resp = UnsubscribeResponse()
112 | self.mns_client.unsubscribe(req, resp)
113 | self.debuginfo(resp)
114 |
115 | def debuginfo(self, resp):
116 | if self.debug:
117 | print("===================DEBUG INFO===================")
118 | print("RequestId: %s" % resp.header["x-mns-request-id"])
119 | print("================================================")
120 |
121 | def __resp2meta__(self, subscription_meta, resp):
122 | subscription_meta.topic_owner = resp.topic_owner
123 | subscription_meta.topic_name = resp.topic_name
124 | subscription_meta.subscription_name = resp.subscription_name
125 | subscription_meta.endpoint = resp.endpoint
126 | subscription_meta.filter_tag = resp.filter_tag
127 | subscription_meta.notify_strategy = resp.notify_strategy
128 | subscription_meta.notify_content_format = resp.notify_content_format
129 | subscription_meta.create_time = resp.create_time
130 | subscription_meta.last_modify_time = resp.last_modify_time
131 |
132 | class SubscriptionMeta:
133 | def __init__(self, endpoint = "", notify_strategy = "", notify_content_format = "", filter_tag = ""):
134 | """ Subscription属性
135 | @note: 设置属性
136 | :: endpoint: 接收端地址, HttpEndpoint, MailEndpoint or QueueEndpoint
137 | :: filter_tag: 消息过滤使用的标签
138 | :: notify_strategy: 向Endpoint推送消息错误时的重试策略
139 | :: notify_content_format: 向Endpoint推送的消息内容格式
140 |
141 | @note: 不可设置属性
142 | :: topic_owner: Subscription订阅的Topic的Owner
143 | :: topic_name: Subscription订阅的Topic名称
144 | :: subscription_name: 订阅名称
145 | :: create_time: Subscription的创建时间,从1970-1-1 00:00:00 000到现在的秒值
146 | :: last_modify_time: 修改Subscription属性信息最近时间,从1970-1-1 00:00:00 000到现在的秒值
147 | """
148 | self.endpoint = endpoint
149 | self.filter_tag = filter_tag
150 | self.notify_strategy = notify_strategy
151 | self.notify_content_format = notify_content_format
152 |
153 | self.topic_owner = ""
154 | self.topic_name = ""
155 | self.subscription_name = ""
156 | self.create_time = -1
157 | self.last_modify_time = -1
158 |
159 | def set_endpoint(self, endpoint):
160 | self.endpoint = endpoint
161 |
162 | def set_filter_tag(self, filter_tag):
163 | self.filter_tag = filter_tag
164 |
165 | def set_notify_strategy(self, notify_strategy):
166 | self.notify_strategy = notify_strategy
167 |
168 | def set_notify_content_format(self, notify_content_format):
169 | self.notify_content_format = notify_content_format
170 |
171 | def __str__(self):
172 | meta_info = {"TopicOwner": self.topic_owner,
173 | "TopicName": self.topic_name,
174 | "SubscriptionName": self.subscription_name,
175 | "Endpoint": self.endpoint,
176 | "FilterTag": self.filter_tag,
177 | "NotifyStrategy": self.notify_strategy,
178 | "NotifyContentFormat": self.notify_content_format,
179 | "CreateTime": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(self.create_time)),
180 | "LastModifyTime": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(self.last_modify_time))}
181 | return "\n".join(["%s: %s" % (k.ljust(30),v) for k,v in meta_info.items()])
182 |
183 | class SubscriptionNotifyStrategy:
184 | BACKOFF = "BACKOFF_RETRY"
185 | EXPONENTIAL = "EXPONENTIAL_DECAY_RETRY"
186 |
187 | class SubscriptionNotifyContentFormat:
188 | XML = "XML"
189 | SIMPLIFIED = "SIMPLIFIED"
190 | JSON = "JSON"
191 |
--------------------------------------------------------------------------------
/mns/account.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | from .mns_client import MNSClient
11 | from .mns_request import *
12 | from .queue import Queue
13 | from .topic import Topic
14 | from .subscription import Subscription
15 | from .mns_tool import MNSLogger
16 |
17 | class Account:
18 | def __init__(self, host, access_id, access_key, security_token = "", debug=False, logger = None):
19 | """
20 | @type host: string
21 | @param host: 访问的url,例如:http://$accountid.mns.cn-hangzhou.aliyuncs.com
22 |
23 | @type access_id: string
24 | @param access_id: 用户的AccessId, 阿里云官网获取
25 |
26 | @type access_key: string
27 | @param access_key: 用户的AccessKey,阿里云官网获取
28 |
29 | @type security_token: string
30 | @param security_token: 如果用户使用STS Token访问,需要提供security_token
31 |
32 | @note: Exception
33 | :: MNSClientParameterException host格式错误
34 | """
35 | self.access_id = access_id
36 | self.access_key = access_key
37 | self.security_token = security_token
38 | self.debug = debug
39 | self.logger = logger
40 | self.mns_client = MNSClient(host, access_id, access_key, security_token = security_token, logger=self.logger)
41 |
42 | def set_debug(self, debug):
43 | self.debug = debug
44 |
45 | def set_log_level(self, log_level):
46 | """ 设置logger的日志级别
47 | @type log_level: int
48 | @param log_level: one of logging.DEBUG,logging.INFO,logging.WARNING,logging.ERROR,logging.CRITICAL
49 | """
50 | MNSLogger.validate_loglevel(log_level)
51 | self.logger.setLevel(log_level)
52 | self.mns_client.set_log_level(log_level)
53 |
54 | def close_log(self):
55 | """ 关闭日志打印
56 | """
57 | self.mns_client.close_log()
58 |
59 | def set_client(self, host, access_id=None, access_key=None, security_token=None):
60 | """ 设置访问的url
61 |
62 | @type host: string
63 | @param host: 访问的url,例如:http://$accountid-new.mns.cn-hangzhou.aliyuncs.com
64 |
65 | @type access_id: string
66 | @param access_id: 用户的AccessId,阿里云官网获取
67 |
68 | @type access_key: string
69 | @param access_key: 用户的AccessKey,阿里云官网获取
70 |
71 | @type security_token: string
72 | @param security_token: 用户使用STS Token访问,需要提供security_token;如果不再使用 STS Token,请设置为 ""
73 |
74 | @note: Exception
75 | :: MNSClientParameterException host格式错误
76 | """
77 | if access_id is None:
78 | access_id = self.access_id
79 | if access_key is None:
80 | access_key = self.access_key
81 | if security_token is None:
82 | security_token = self.security_token
83 | self.mns_client = MNSClient(host, access_id, access_key, security_token=security_token, logger=self.logger)
84 |
85 | def set_attributes(self, account_meta, req_info=None):
86 | """ 设置Account的属性
87 |
88 | @type account_meta: AccountMeta object
89 | @param queue_meta: 新设置的属性
90 |
91 | @type req_info: RequestInfo object
92 | @param req_info: 透传到MNS的请求信息
93 |
94 | @note: Exception
95 | :: MNSClientParameterException 参数格式异常
96 | :: MNSClientNetworkException 网络异常
97 | :: MNSServerException mns处理异常
98 | """
99 | req = SetAccountAttributesRequest(account_meta.logging_bucket)
100 | req.set_req_info(req_info)
101 | resp = SetAccountAttributesResponse()
102 | self.mns_client.set_account_attributes(req, resp)
103 | self.debuginfo(resp)
104 |
105 | def get_attributes(self, req_info=None):
106 | """ 获取Account的属性
107 |
108 | @rtype: AccountMeta object
109 | @return: 返回该Account的Meta属性
110 |
111 | @type req_info: RequestInfo object
112 | @param req_info: 透传到MNS的请求信息
113 |
114 | @note: Exception
115 | :: MNSClientNetworkException 网络异常
116 | :: MNSServerException mns处理异常
117 | """
118 | req = GetAccountAttributesRequest()
119 | req.set_req_info(req_info)
120 | resp = GetAccountAttributesResponse()
121 | self.mns_client.get_account_attributes(req, resp)
122 | account_meta = AccountMeta()
123 | self.__resp2meta__(account_meta, resp)
124 | self.debuginfo(resp)
125 | return account_meta
126 |
127 | def get_queue(self, queue_name):
128 | """ 获取Account的一个Queue对象
129 |
130 | @type queue_name: string
131 | @param queue_name: 队列名
132 |
133 | @rtype: Queue object
134 | @return: 返回该Account的一个Queue对象
135 | """
136 | return Queue(queue_name, self.mns_client, self.debug)
137 |
138 | def get_topic(self, topic_name):
139 | """ 获取Account的一个Topic对象
140 |
141 | @type topic_name: string
142 | @param topic_name: 主题名称
143 |
144 | @rtype: Topic object
145 | @return: 返回该Account的一个Topic对象
146 | """
147 | return Topic(topic_name, self.mns_client, self.debug)
148 |
149 | def get_subscription(self, topic_name, subscription_name):
150 | """ 获取Account的一个Subscription对象
151 |
152 | @type topic_name: string
153 | @param topic_name: 主题名称
154 |
155 | @type subscription_name: string
156 | @param subscription_name: 订阅名称
157 |
158 | @rtype: Subscription object
159 | @return: 返回该Account指定Topic的一个Subscription对象
160 | """
161 | return Subscription(topic_name, subscription_name, self.mns_client, self.debug)
162 |
163 | def get_client(self):
164 | """ 获取queue client
165 |
166 | @rtype: MNSClient object
167 | @return: 返回使用的MNSClient object
168 | """
169 | return self.mns_client
170 |
171 | def list_queue(self, prefix = "", ret_number = -1, marker = "", req_info=None):
172 | """ 列出Account的队列
173 |
174 | @type prefix: string
175 | @param prefix: 队列名的前缀
176 |
177 | @type ret_number: int
178 | @param ret_number: list_queue最多返回的队列数
179 |
180 | @type marker: string
181 | @param marker: list_queue的起始位置,上次list_queue返回的next_marker
182 |
183 | @rtype: tuple
184 | @return: QueueURL的列表和下次list queue的起始位置; 如果所有queue都list出来,next_marker为"".
185 |
186 | @type req_info: RequestInfo object
187 | @param req_info: 透传到MNS的请求信息
188 |
189 | @note: Exception
190 | :: MNSClientParameterException 参数格式异常
191 | :: MNSClientNetworkException 网络异常
192 | :: MNSServerException mns处理异常
193 | """
194 | req = ListQueueRequest(prefix, ret_number, marker)
195 | req.set_req_info(req_info)
196 | resp = ListQueueResponse()
197 | self.mns_client.list_queue(req, resp)
198 | self.debuginfo(resp)
199 | return resp.queueurl_list, resp.next_marker
200 |
201 | def list_topic(self, prefix = "", ret_number = -1, marker = "", req_info=None):
202 | """ 列出Account的主题
203 |
204 | @type prefix: string
205 | @param prefix: 主题名称的前缀
206 |
207 | @type ret_number: int
208 | @param ret_number: list_topic最多返回的主题个数
209 |
210 | @type marker: string
211 | @param marker: list_topic的起始位置,上次list_topic返回的next_marker
212 |
213 | @rtype: tuple
214 | @return: TopicURL的列表,下次list topic的起始位置, 如果所有主题都返回时,next_marker为""
215 |
216 | @type req_info: RequestInfo object
217 | @param req_info: 透传到MNS的请求信息
218 |
219 | @note: Exception
220 | :: MNSClientParameterException 参数格式异常
221 | :: MNSClientNetworkException 网络异常
222 | :: MNSServerException mns处理异常
223 | """
224 | req = ListTopicRequest(prefix, ret_number, marker, True)
225 | req.set_req_info(req_info)
226 | resp = ListTopicResponse()
227 | self.mns_client.list_topic(req, resp)
228 | self.debuginfo(resp)
229 | return resp.topicurl_list, resp.next_marker
230 |
231 | def open_service(self, req_info=None):
232 | req = OpenServiceRequest()
233 | req.set_req_info(req_info)
234 | resp = OpenServiceResponse()
235 | self.mns_client.open_service(req, resp)
236 | return resp
237 |
238 | def debuginfo(self, resp):
239 | if self.debug:
240 | print("===================DEBUG INFO===================")
241 | print("RequestId: %s" % resp.header["x-mns-request-id"])
242 | print("================================================")
243 |
244 | def __resp2meta__(self, account_meta, resp):
245 | account_meta.logging_bucket = resp.logging_bucket
246 |
247 | class AccountMeta:
248 | def __init__(self, logging_bucket = None):
249 | """ Account属性
250 | @note: 可设置属性
251 | :: logging_bucket: 保存用户操作MNS日志的bucket name
252 | """
253 | self.logging_bucket = logging_bucket
254 |
255 | def __str__(self):
256 | meta_info = {"LoggingBucket" : self.logging_bucket}
257 | return "\n".join(["%s: %s" % (k.ljust(30),v) for k,v in meta_info.items()])
258 |
--------------------------------------------------------------------------------
/sample/server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 | # Copyright (C) 2015, Alibaba Cloud Computing
4 |
5 | #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:
6 |
7 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | #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.
10 |
11 | import sys
12 | import cgi
13 | import shutil
14 | import socket
15 | import base64
16 | import logging
17 | try:
18 | from urllib.request import urlopen
19 | except ImportError:
20 | from urllib2 import urlopen
21 | except Exception as err:
22 | raise(err)
23 | #import M2Crypto
24 | #from cryptography import x509
25 | #from cryptography.hazmat.primitives import hashes, serialization
26 | #from cryptography.hazmat.backends import default_backend
27 | #from cryptography.hazmat.primitives.asymmetric import padding
28 | #import cryptography
29 |
30 | import logging.handlers
31 | import xml.dom.minidom
32 | #import BaseHTTPServer
33 | try:
34 | from http.server import BaseHTTPRequestHandler, HTTPServer
35 | except ImportError:
36 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
37 | except Exception as err:
38 | raise(err)
39 |
40 | #import SocketServer
41 | try:
42 | import socketserver as SocketServer
43 | except ImportError:
44 | import SocketServer as SocketServer
45 |
46 | from sample_common import MNSSampleCommon
47 |
48 | __version__ = "1.0.3"
49 | _LOGGER = logging.getLogger(__name__)
50 |
51 | #class SimpleHttpNotifyEndpoint(BaseHTTPServer.BaseHTTPRequestHandler):
52 | class SimpleHttpNotifyEndpoint(BaseHTTPRequestHandler):
53 | server_version = "SimpleHttpNotifyEndpoint/" + __version__
54 | access_log_file = "access_log"
55 | msg_type = "XML"
56 |
57 | def do_POST(self):
58 | #content_length = int(self.headers.getheader('content-length', 0))
59 | content_length = int(self.headers['content-length'])
60 | self.req_body = self.rfile.read(content_length)
61 | self.msg = NotifyMessage()
62 | _LOGGER.info("Headers:%s\nBody:%s" % (self.headers, self.req_body))
63 | if not self.authenticate():
64 | res_code = 403
65 | res_content = u"Access Forbidden"
66 | _LOGGER.warning("Access Forbidden!\nHeaders:%s\nReqBody:%s\n" % (self.headers, self.req_body))
67 | elif not self.validateBody(self.req_body, self.msg, self.msg_type):
68 | res_code = 400
69 | res_content = "Invalid Notify Message"
70 | _LOGGER.warning("Invalid Notify Message!\nHeaders:%s\nReqBody:%s\n" % (self.headers, self.req_body))
71 | else:
72 | res_code = 201
73 | res_content = u""
74 | _LOGGER.info("Notify Message Succeed!\n%s" % self.msg)
75 | self.access_log(res_code)
76 | self.response(res_code, res_content)
77 |
78 | def authenticate(self):
79 | #get string to signature
80 | service_str = "\n".join(sorted(["%s:%s" % (k,v) for k,v in self.headers.items() if k.startswith("x-mns-")]))
81 | sign_header_list = []
82 | for key in ["content-md5", "content-type", "date"]:
83 | if key in self.headers.keys():
84 | sign_header_list.append(self.headers[key])
85 | else:
86 | sign_header_list.append("")
87 | str2sign = u"%s\n%s\n%s\n%s" % (self.command, "\n".join(sign_header_list), service_str, self.path)
88 |
89 | #verify
90 | #authorization = self.headers.getheader('Authorization')
91 | authorization = self.headers['Authorization']
92 | signature = base64.b64decode(authorization)
93 | cert = urlopen(base64.b64decode(self.headers['x-mns-signing-cert-url']).decode('utf-8')).read()
94 | cert_str = cert.decode('utf-8')
95 |
96 | from Crypto.Util.asn1 import DerSequence
97 | from Crypto.PublicKey import RSA
98 | from Crypto.Signature import PKCS1_v1_5
99 | from Crypto.Hash import SHA
100 | from binascii import a2b_base64
101 |
102 | # Convert from PEM to DER
103 | lines = cert_str.replace(" ",'').split()
104 | der = a2b_base64(''.join(lines[1:-1]))
105 |
106 | # Extract subjectPublicKeyInfo field from X.509 certificate (see RFC3280)
107 | cert = DerSequence()
108 | cert.decode(der)
109 | tbsCertificate = DerSequence()
110 | tbsCertificate.decode(cert[0])
111 | subjectPublicKeyInfo = tbsCertificate[6]
112 |
113 | # Initialize RSA key
114 | key = RSA.importKey(subjectPublicKeyInfo)
115 | h = SHA.new(str2sign.encode('utf-8'))
116 | verifier = PKCS1_v1_5.new(key)
117 | if verifier.verify(h, signature):
118 | return True
119 | return False
120 |
121 |
122 | def validateBody(self, data, msg, type):
123 | if type == "XML":
124 | return self.xml_decode(data, msg)
125 | else:
126 | msg.message = data
127 | return True
128 |
129 | def xml_decode(self, data, msg):
130 | if data == "":
131 | logger.error("Data is \"\".")
132 | return False
133 | try:
134 | dom = xml.dom.minidom.parseString(data)
135 | except Exception as e:
136 | _LOGGER.error("Parse string fail, exception:%s" % e)
137 | return False
138 |
139 | node_list = dom.getElementsByTagName("Notification")
140 | if not node_list:
141 | _LOGGER.error("Get node of \"Notification\" fail:%s" % e)
142 | return False
143 |
144 | data_dic = {}
145 | for node in node_list[0].childNodes:
146 | if node.nodeName != "#text" and node.childNodes != []:
147 | data_dic[node.nodeName] = node.firstChild.toxml().encode('utf-8')
148 |
149 | key_list = ["TopicOwner", "TopicName", "Subscriber", "SubscriptionName", "MessageId", "MessageMD5", "Message", "PublishTime"]
150 | for key in key_list:
151 | if key not in data_dic.keys():
152 | _LOGGER.error("Check item fail. Need \"%s\"." % key)
153 | return False
154 |
155 | msg.topic_owner = data_dic["TopicOwner"]
156 | msg.topic_name = data_dic["TopicName"]
157 | msg.subscriber = data_dic["Subscriber"]
158 | msg.subscription_name = data_dic["SubscriptionName"]
159 | msg.message_id = data_dic["MessageId"]
160 | msg.message_md5 = data_dic["MessageMD5"]
161 | msg.message_tag = data_dic["MessageTag"] if data_dic.has_key("MessageTag") else ""
162 | msg.message = data_dic["Message"]
163 | msg.publish_time = data_dic["PublishTime"]
164 | return True
165 |
166 | def response(self, response_code, response_content):
167 | self.send_response(response_code)
168 | self.send_header("Content-type", "text/html")
169 | self.send_header("Content-Length", str(len(response_content)))
170 | self.end_headers()
171 | self.wfile.write(response_content.encode('utf-8'))
172 |
173 | def send_response(self, code, message=None):
174 | """Send the response header and log the response code.
175 |
176 | Also send two standard headers with the server software
177 | version and the current date.
178 |
179 | """
180 | if message is None:
181 | if code in self.responses:
182 | message = self.responses[code][0]
183 | else:
184 | message = u''
185 | if self.request_version != 'HTTP/0.9':
186 | self.wfile.write((u"%s %d %s\r\n" %
187 | (self.protocol_version, code, message)).encode('utf-8'))
188 | # print (self.protocol_version, code, message)
189 | self.send_header('Server', self.version_string())
190 | self.send_header('Date', self.date_time_string())
191 |
192 | def access_log(self, res_code):
193 | """
194 | access_log format: time method res_code path req_http_version req_length req_host req_agent mns_reqid mns_version
195 | """
196 | item_list = [self.command, res_code, self.path, self.request_version]
197 | header_key_list = ["Content-Length", "Host", "User-Agent", "x-mns-request-id", "x-mns-version"]
198 | for key in header_key_list:
199 | #if self.headers.has_key(key):
200 | if key in self.headers:
201 | #item_list.append(self.headers.getheader(key))
202 | item_list.append(self.headers[key])
203 | else:
204 | item_list.append("-")
205 | acc_log = "[%s]" % self.log_date_time_string() + " ".join(["\"%s\"" % item for item in item_list]) + "\n"
206 | print(acc_log)
207 | open(self.access_log_file, 'a').write(acc_log)
208 |
209 | #class ThreadedHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
210 | class ThreadedHTTPServer(SocketServer.ThreadingMixIn, HTTPServer):
211 | '''Handle request in a separated thread.'''
212 |
213 | class NotifyMessage:
214 | def __init__(self):
215 | self.topic_owner = ""
216 | self.topic_name = ""
217 | self.subscriber = ""
218 | self.subscription_name = ""
219 | self.message_id = ""
220 | self.message_md5 = ""
221 | self.message_tag = ""
222 | self.message = ""
223 | self.publish_time = -1
224 |
225 | def __str__(self):
226 | msg_info = {"TopicOwner" : self.topic_owner,
227 | "TopicName" : self.topic_name,
228 | "Subscriber" : self.subscriber,
229 | "SubscriptionName" : self.subscription_name,
230 | "MessageId" : self.message_id,
231 | "MessageMD5" : self.message_md5,
232 | "MessageTag" : self.message_tag,
233 | "Message" : self.message,
234 | "PublishTime" : self.publish_time}
235 | return "\n".join(["%s: %s"%(k.ljust(30),v) for k,v in msg_info.items()])
236 |
237 |
238 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/mns/topic.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | import time
11 | import base64
12 | from .mns_client import MNSClient
13 | from .mns_request import *
14 | from .mns_exception import *
15 | from .subscription import *
16 | try:
17 | import json
18 | except ImportError:
19 | import simplejson as json
20 |
21 | class Topic:
22 | def __init__(self, topic_name, mns_client, debug=False):
23 | self.topic_name = topic_name
24 | self.mns_client = mns_client
25 | self.debug = debug
26 |
27 | def set_debug(self, debug):
28 | self.debug = debug
29 |
30 | def get_subscription(self, subscription_name):
31 | """ 获取Topic的一个Subscription对象
32 |
33 | @type subscription_name: string
34 | @param subscription_name: 订阅名称
35 |
36 | @rtype: Subscription object
37 | @return: 返回该Topic的一个Subscription对象
38 | """
39 | return Subscription(self.topic_name, subscription_name, self.mns_client, self.debug)
40 |
41 | def create(self, topic_meta, req_info=None):
42 | """ 创建主题
43 |
44 | @type topic_meta: TopicMeta object
45 | @param topic_meta: TopicMeta对象,指定主题的属性
46 |
47 | @type req_info: RequestInfo object
48 | @param req_info: 透传到MNS的请求信息
49 |
50 | @rtype: string
51 | @return 新创建队列的URL
52 |
53 | @note: Exception
54 | :: MNSClientParameterException 参数格式异常
55 | :: MNSClientNetworkException 网络异常
56 | :: MNSServerException mns处理异常
57 | """
58 | req = CreateTopicRequest(self.topic_name, topic_meta.maximum_message_size, topic_meta.logging_enabled)
59 | req.set_req_info(req_info)
60 | resp = CreateTopicResponse()
61 | self.mns_client.create_topic(req, resp)
62 | self.debuginfo(resp)
63 | return resp.topic_url
64 |
65 | def get_attributes(self, req_info=None):
66 | """ 获取主题属性
67 |
68 | @type req_info: RequestInfo object
69 | @param req_info: 透传到MNS的请求信息
70 |
71 | @rtype: TopicMeta object
72 | @return 主题的属性
73 |
74 | @note: Exception
75 | :: MNSClientNetworkException 网络异常
76 | :: MNSServerException mns处理异常
77 | """
78 | req = GetTopicAttributesRequest(self.topic_name)
79 | req.set_req_info(req_info)
80 | resp = GetTopicAttributesResponse()
81 | self.mns_client.get_topic_attributes(req, resp)
82 | topic_meta = TopicMeta()
83 | self.__resp2meta__(topic_meta, resp)
84 | self.debuginfo(resp)
85 | return topic_meta
86 |
87 | def set_attributes(self, topic_meta, req_info=None):
88 | """ 设置队列属性
89 |
90 | @type topic_meta: TopicMeta object
91 | @param topic_meta: 新设置的主题属性
92 |
93 | @type req_info: RequestInfo object
94 | @param req_info: 透传到MNS的请求信息
95 |
96 | @note: Exception
97 | :: MNSClientParameterException 参数格式异常
98 | :: MNSClientNetworkException 网络异常
99 | :: MNSServerException mns处理异常
100 | """
101 | req = SetTopicAttributesRequest(self.topic_name, topic_meta.maximum_message_size, topic_meta.logging_enabled)
102 | req.set_req_info(req_info)
103 | resp = SetTopicAttributesResponse()
104 | self.mns_client.set_topic_attributes(req, resp)
105 | self.debuginfo(resp)
106 |
107 | def delete(self, req_info=None):
108 | """ 删除主题
109 |
110 | @type req_info: RequestInfo object
111 | @param req_info: 透传到MNS的请求信息
112 |
113 | @note: Exception
114 | :: MNSClientNetworkException 网络异常
115 | :: MNSServerException mns处理异常
116 | """
117 | req = DeleteTopicRequest(self.topic_name)
118 | req.set_req_info(req_info)
119 | resp = DeleteTopicResponse()
120 | self.mns_client.delete_topic(req, resp)
121 | self.debuginfo(resp)
122 |
123 | def publish_message(self, message, req_info=None):
124 | """ 发送消息
125 |
126 | @type message: TopicMessage object
127 | @param message: 发布的TopicMessage object
128 |
129 | @type req_info: RequestInfo object
130 | @param req_info: 透传到MNS的请求信息
131 |
132 | @rtype: TopicMessage object
133 | @return: 消息发布成功的返回属性,包含MessageId和MessageBodyMD5
134 |
135 | @note: Exception
136 | :: MNSClientParameterException 参数格式异常
137 | :: MNSClientNetworkException 网络异常
138 | :: MNSServerException mns处理异常
139 | """
140 | req = PublishMessageRequest(self.topic_name, message.message_body, message.message_tag, message.direct_mail, message.direct_sms)
141 | req.set_req_info(req_info)
142 | resp = PublishMessageResponse()
143 | self.mns_client.publish_message(req, resp)
144 | self.debuginfo(resp)
145 | return self.__publish_resp2msg__(resp)
146 |
147 | def list_subscription(self, prefix = "", ret_number = -1, marker = "", req_info=None):
148 | """ 列出该主题的订阅
149 |
150 | @type prefix: string
151 | @param prefix: 订阅名称的前缀
152 |
153 | @type ret_number: int
154 | @param ret_number: list_subscription最多返回的订阅个数
155 |
156 | @type marker: string
157 | @param marker: list_subscriptiond的起始位置,上次list_subscription返回的next_marker
158 |
159 | @type req_info: RequestInfo object
160 | @param req_info: 透传到MNS的请求信息
161 |
162 | @rtype: tuple
163 | @return SubscriptionURL的列表,下次list subscription的起始位置;当所有订阅都返回时,next_marker为""
164 |
165 | @note: Exception
166 | :: MNSClientParameterException 参数格式异常
167 | :: MNSClientNetworkException 网络异常
168 | :: MNSServerException mns处理异常
169 | """
170 | req = ListSubscriptionByTopicRequest(self.topic_name, prefix, ret_number, marker)
171 | req.set_req_info(req_info)
172 | resp = ListSubscriptionByTopicResponse()
173 | self.mns_client.list_subscription_by_topic(req, resp)
174 | self.debuginfo(resp)
175 | return resp.subscriptionurl_list, resp.next_marker
176 |
177 | def debuginfo(self, resp):
178 | if self.debug:
179 | print("===================DEBUG INFO===================")
180 | print("RequestId: %s" % resp.header["x-mns-request-id"])
181 | print("================================================")
182 |
183 | def __resp2meta__(self, topic_meta, resp):
184 | topic_meta.message_count = resp.message_count
185 | topic_meta.create_time = resp.create_time
186 | topic_meta.last_modify_time = resp.last_modify_time
187 | topic_meta.maximum_message_size = resp.maximum_message_size
188 | topic_meta.message_retention_period = resp.message_retention_period
189 | topic_meta.topic_name = resp.topic_name
190 | topic_meta.logging_enabled = resp.logging_enabled
191 |
192 | def __publish_resp2msg__(self, resp):
193 | msg = TopicMessage()
194 | msg.message_id = resp.message_id
195 | msg.message_body_md5 = resp.message_body_md5
196 | return msg
197 |
198 | class TopicMeta:
199 | def __init__(self, maximum_message_size = -1, logging_enabled = None):
200 | """ 主题属性
201 | @note:设置属性
202 | :: maximum_message_size: message body的最大长度,单位:Byte
203 | :: logging_enabled: 是否开启logging功能,如果开启MNS将该主题的日志推送到Account的logging bucket中
204 |
205 | @note: 不可设置属性
206 | :: message_retention_period: message最长存活时间,单位:秒
207 | :: message_count: topic中的消息数
208 | :: create_time: topic创建时间,单位:秒
209 | :: last_modify_time: 修改topic属性的最近时间,单位:秒
210 | :: topic_name: 主题名称
211 | """
212 | self.maximum_message_size = maximum_message_size
213 | self.logging_enabled = logging_enabled
214 |
215 | self.message_retention_period = -1
216 | self.message_count = -1
217 | self.create_time = -1
218 | self.last_modify_time = -1
219 | self.topic_name = ""
220 |
221 | def set_maximum_message_size(self, maximum_message_size):
222 | self.maximum_message_size = maximum_message_size
223 |
224 | def set_logging_enabled(self, logging_enabled):
225 | self.logging_enabled = logging_enabled
226 |
227 | def __str__(self):
228 | meta_info = {"MaximumMessageSize": self.maximum_message_size,
229 | "MessageRetentionPeriod": self.message_retention_period,
230 | "MessageCount": self.message_count,
231 | "CreateTime": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(self.create_time)),
232 | "LastModifyTime": time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(self.last_modify_time)),
233 | "TopicName": self.topic_name,
234 | "LoggingEnabled": self.logging_enabled}
235 | return "\n".join(["%s: %s" % (k.ljust(30),v) for k,v in meta_info.items()])
236 |
237 | class TopicMessage(object):
238 | def __init__(self, message_body = u"", message_tag = u"", direct_mail = None, direct_sms = None):
239 | """ Specify information of TopicMessage
240 |
241 | @note: publish_message params
242 | :: message_body string
243 | :: message_tag string, used to filter message
244 | :: direct_mail DirectMailInfo, the information of direct mail
245 | :: direct_sms DirectSMSInfo, the information of direct sms
246 |
247 | @note: publish_message response information
248 | :: message_id
249 | :: message_body_md5
250 | """
251 | # None 处理, 避免生成 xml 数据时的 TypeError 以及 base64编码 时的 None 问题
252 | if message_body is None:
253 | raise MNSClientParameterException("MessageBodyInvalid", "Bad value: None, message body should not be None.")
254 |
255 | self.message_body = message_body
256 | self.message_tag = message_tag
257 | self.direct_mail = direct_mail
258 | self.direct_sms = direct_sms
259 |
260 | self.message_id = u""
261 | self.message_body_md5 = u""
262 |
263 | def set_messagebody(self, message_body):
264 | self.message_body = message_body
265 |
266 | def get_messagebody(self):
267 | return self.message_body
268 |
269 | def set_message_tag(self, message_tag):
270 | self.message_tag = message_tag
271 |
272 | class Base64TopicMessage(TopicMessage):
273 | def __init__(self, message_body = u"", message_tag = u"", direct_mail = None, direct_sms = None):
274 | """ Specify information of Base64TopicMessage
275 |
276 | @note: publish_message params
277 | :: message_body string
278 | :: message_tag string, used to filter message
279 | :: direct_mail DirectMailInfo, the information of direct mail
280 | :: direct_sms DirectSMSInfo, the information of direct sms
281 |
282 | """
283 | super(Base64TopicMessage, self).__init__(message_body, message_tag, direct_mail, direct_sms)
284 |
285 | self.message_body = base64.b64encode(self.message_body.encode("utf-8")).decode("utf-8")
286 |
287 | def set_messagebody(self, message_body):
288 | self.message_body = base64.b64encode(message_body.encode("utf-8")).decode("utf-8")
289 |
290 | def get_messagebody(self):
291 | return base64.b64decode(self.message_body.encode("utf-8")).decode("utf-8")
292 |
293 | class DirectMailInfo:
294 | def __init__(self, account_name, subject, address_type, is_html, reply_to_address):
295 | """ Specify information of DirectMail
296 |
297 | @type account_name: string
298 | @param account_name: the name of transmission account
299 |
300 | @type subject: string
301 | @param subject: the subject of mail
302 |
303 | @type address_type: int
304 | @param address_type: 0 or 1
305 |
306 | @type is_html: int
307 | @param is_html: 0 or 1
308 |
309 | @type reply_to_address: int
310 | @param reply_to_address: 0 or 1
311 |
312 | @note: go https://help.aliyun.com/document_detail/29444.html to get more information
313 |
314 | """
315 | self.account_name = account_name
316 | self.subject = subject
317 | self.address_type = address_type
318 | self.is_html = is_html
319 | self.reply_to_address = reply_to_address
320 |
321 | def get(self):
322 | return {"AccountName": self.account_name, \
323 | "Subject": self.subject, \
324 | "AddressType": self.address_type, \
325 | "IsHtml": self.is_html,\
326 | "ReplyToAddress": self.reply_to_address}
327 |
328 | class DirectSMSInfo:
329 | SINGLE_CONTENT = "singleContent"
330 | MULTI_CONTENT = "multiContent"
331 |
332 | def __init__(self, free_sign_name, template_code, single):
333 | """ Specify information of DirectSMS
334 |
335 | @type free_sign name: string
336 | @param free_sign_name: the name of sign, you can list from console
337 |
338 | @type template_code: string
339 | @param template_code: the code of template, you can list from console
340 |
341 | @type single: bool
342 | @param single: the type of SMS is singleContent or not
343 | """
344 | self.free_sign_name = free_sign_name
345 | self.template_code = template_code
346 | if single:
347 | self.type = DirectSMSInfo.SINGLE_CONTENT
348 | else:
349 | self.type = DirectSMSInfo.MULTI_CONTENT
350 | self.receivers = set([])
351 | self.sms_params = {}
352 |
353 | def add_receiver(self, receiver, params=None):
354 | """
355 | @type receiver: string
356 | @param receiver: the phone number of receiver
357 |
358 | @type params: dict
359 | @param params: specify params for receiver, such ad: {"key1":"value1", "key2":"value2"}
360 | """
361 | if self.type == DirectSMSInfo.SINGLE_CONTENT:
362 | self.receivers.add(receiver)
363 | else:
364 | if params is not None:
365 | self.sms_params[receiver] = params
366 | else:
367 | self.sms_params[receiver] = {}
368 |
369 | def set_params(self, params):
370 | self.sms_params = params
371 |
372 | def get(self):
373 | info = {"FreeSignName": self.free_sign_name,\
374 | "TemplateCode": self.template_code,\
375 | "Type": self.type,\
376 | "Receiver": ','.join(self.receivers),\
377 | "SmsParams": json.dumps(self.sms_params)}
378 | return info
379 |
--------------------------------------------------------------------------------
/mns/mns_request.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | class RequestBase:
11 | def __init__(self):
12 | self.method = ""
13 | self.request_id = None
14 |
15 | def set_req_info(self, req_info):
16 | if req_info is not None:
17 | if req_info.request_id is not None:
18 | self.request_id = req_info.request_id
19 |
20 | class ResponseBase():
21 | def __init__(self):
22 | self.status = -1
23 | self.header = {}
24 | self.error_data = ""
25 |
26 | def get_requestid(self):
27 | return self.header.get("x-mns-request-id")
28 |
29 | class SetAccountAttributesRequest(RequestBase):
30 | def __init__(self, logging_bucket = None):
31 | RequestBase.__init__(self)
32 | self.logging_bucket = logging_bucket
33 | self.method = "PUT"
34 |
35 | class SetAccountAttributesResponse(ResponseBase):
36 | def __init__(self):
37 | ResponseBase.__init__(self)
38 |
39 | class GetAccountAttributesRequest(RequestBase):
40 | def __init__(self):
41 | RequestBase.__init__(self)
42 | self.method = "GET"
43 |
44 | class GetAccountAttributesResponse(ResponseBase):
45 | def __init__(self):
46 | ResponseBase.__init__(self)
47 | self.logging_bucket = ""
48 |
49 | class CreateQueueRequest(RequestBase):
50 | def __init__(self, queue_name, visibility_timeout = -1, maximum_message_size = -1, message_retention_period = -1, delay_seconds = -1, polling_wait_seconds = -1, logging_enabled = None):
51 | RequestBase.__init__(self)
52 | self.queue_name = queue_name
53 | self.visibility_timeout = visibility_timeout
54 | self.maximum_message_size = maximum_message_size
55 | self.message_retention_period = message_retention_period
56 | self.delay_seconds = delay_seconds
57 | self.polling_wait_seconds = polling_wait_seconds
58 | self.logging_enabled = logging_enabled
59 | self.method = "PUT"
60 |
61 | class CreateQueueResponse(ResponseBase):
62 | def __init__(self):
63 | ResponseBase.__init__(self)
64 | self.queue_url = ""
65 |
66 | class DeleteQueueRequest(RequestBase):
67 | def __init__(self, queue_name):
68 | RequestBase.__init__(self)
69 | self.queue_name = queue_name
70 | self.method = "DELETE"
71 |
72 | class DeleteQueueResponse(ResponseBase):
73 | def __init__(self):
74 | ResponseBase.__init__(self)
75 |
76 | class ListQueueRequest(RequestBase):
77 | def __init__(self, prefix = u"", ret_number = -1, marker = u"", with_meta = False):
78 | RequestBase.__init__(self)
79 | self.prefix = prefix
80 | self.ret_number = ret_number
81 | self.marker = marker
82 | self.with_meta = with_meta
83 | self.method = "GET"
84 |
85 | class ListQueueResponse(ResponseBase):
86 | def __init__(self):
87 | ResponseBase.__init__(self)
88 | self.queueurl_list = []
89 | self.next_marker = u""
90 | self.queuemeta_list = []
91 |
92 | class SetQueueAttributesRequest(RequestBase):
93 | def __init__(self, queue_name, visibility_timeout = -1, maximum_message_size = -1, message_retention_period = -1, delay_seconds = -1, polling_wait_seconds = -1, logging_enabled = None):
94 | RequestBase.__init__(self)
95 | self.queue_name = queue_name
96 | self.visibility_timeout = visibility_timeout
97 | self.maximum_message_size = maximum_message_size
98 | self.message_retention_period = message_retention_period
99 | self.delay_seconds = delay_seconds
100 | self.polling_wait_seconds = polling_wait_seconds
101 | self.logging_enabled = logging_enabled
102 | self.method = "PUT"
103 |
104 | class SetQueueAttributesResponse(ResponseBase):
105 | def __init__(self):
106 | ResponseBase.__init__(self)
107 |
108 | class GetQueueAttributesRequest(RequestBase):
109 | def __init__(self, queue_name):
110 | RequestBase.__init__(self)
111 | self.queue_name = queue_name
112 | self.method = "GET"
113 |
114 | class GetQueueAttributesResponse(ResponseBase):
115 | def __init__(self):
116 | ResponseBase.__init__(self)
117 | self.active_messages = -1
118 | self.create_time = -1
119 | self.delay_messages = -1
120 | self.delay_seconds = -1
121 | self.inactive_messages = -1
122 | self.last_modify_time = -1
123 | self.maximum_message_size = -1
124 | self.message_retention_period = -1
125 | self.queue_name = ""
126 | self.visibility_timeout = -1
127 | self.polling_wait_seconds = -1
128 | self.logging_enable = None
129 |
130 | class SendMessageRequest(RequestBase):
131 | def __init__(self, queue_name, message_body, delay_seconds = -1, priority = -1, base64encode = True):
132 | RequestBase.__init__(self)
133 | self.queue_name = queue_name
134 | self.message_body = message_body
135 | self.delay_seconds = delay_seconds
136 | self.priority = priority
137 | self.base64encode = base64encode
138 | self.method = "POST"
139 |
140 | class SendMessageResponse(ResponseBase):
141 | def __init__(self):
142 | ResponseBase.__init__(self)
143 | self.message_id = ""
144 | self.message_body_md5 = ""
145 | self.receipt_handle = ""
146 |
147 | class SendMessageRequestEntry:
148 | def __init__(self, message_body, delay_seconds = -1, priority = -1):
149 | self.message_body = message_body
150 | self.delay_seconds = delay_seconds
151 | self.priority = priority
152 |
153 | class BatchSendMessageRequest(RequestBase):
154 | def __init__(self, queue_name, base64encode):
155 | RequestBase.__init__(self)
156 | self.queue_name = queue_name
157 | self.base64encode = base64encode
158 | self.method = "POST"
159 | self.message_list = []
160 |
161 | def add_message(self, message_body, delay_seconds = -1, priority = -1):
162 | msg = SendMessageRequestEntry(message_body, delay_seconds, priority)
163 | self.message_list.append(msg)
164 |
165 | class SendMessageResponseEntry:
166 | def __init__(self):
167 | self.message_id = ""
168 | self.message_body_md5 = ""
169 |
170 | class BatchSendMessageResponse(ResponseBase):
171 | def __init__(self):
172 | ResponseBase.__init__(self)
173 | self.message_list = []
174 |
175 | class PeekMessageRequest(RequestBase):
176 | def __init__(self, queue_name, base64decode = True):
177 | RequestBase.__init__(self)
178 | self.queue_name = queue_name
179 | self.base64decode = base64decode
180 | self.method = "GET"
181 |
182 | class PeekMessageResponse(ResponseBase):
183 | def __init__(self):
184 | ResponseBase.__init__(self)
185 | self.dequeue_count = -1
186 | self.enqueue_time = -1
187 | self.first_dequeue_time = -1
188 | self.message_body = ""
189 | self.message_id = ""
190 | self.message_body_md5 = ""
191 | self.priority = -1
192 |
193 | class BatchPeekMessageRequest(RequestBase):
194 | def __init__(self, queue_name, batch_size, base64decode = True):
195 | RequestBase.__init__(self)
196 | self.queue_name = queue_name
197 | self.batch_size = batch_size
198 | self.base64decode = base64decode
199 | self.method = "GET"
200 |
201 | class PeekMessageResponseEntry:
202 | def __init__(self):
203 | self.dequeue_count = -1
204 | self.enqueue_time = -1
205 | self.first_dequeue_time = -1
206 | self.message_body = ""
207 | self.message_id = ""
208 | self.message_body_md5 = ""
209 | self.priority = -1
210 |
211 | class BatchPeekMessageResponse(ResponseBase):
212 | def __init__(self):
213 | ResponseBase.__init__(self)
214 | self.message_list = []
215 |
216 | class ReceiveMessageRequest(RequestBase):
217 | def __init__(self, queue_name, base64decode = True, wait_seconds = -1):
218 | RequestBase.__init__(self)
219 | self.queue_name = queue_name
220 | self.base64decode = base64decode
221 | self.wait_seconds = wait_seconds
222 | self.method = "GET"
223 |
224 | class ReceiveMessageResponse(PeekMessageResponse):
225 | def __init__(self):
226 | PeekMessageResponse.__init__(self)
227 | self.next_visible_time = -1
228 | self.receipt_handle = ""
229 |
230 | class BatchReceiveMessageRequest(RequestBase):
231 | def __init__(self, queue_name, batch_size, base64decode = True, wait_seconds = -1):
232 | RequestBase.__init__(self)
233 | self.queue_name = queue_name
234 | self.batch_size = batch_size
235 | self.base64decode = base64decode
236 | self.wait_seconds = wait_seconds
237 | self.method = "GET"
238 |
239 | class ReceiveMessageResponseEntry():
240 | def __init__(self):
241 | self.dequeue_count = -1
242 | self.enqueue_time = -1
243 | self.first_dequeue_time = -1
244 | self.message_body = ""
245 | self.message_id = ""
246 | self.message_body_md5 = ""
247 | self.priority = -1
248 | self.next_visible_time = ""
249 | self.receipt_handle = ""
250 |
251 | class BatchReceiveMessageResponse(ResponseBase):
252 | def __init__(self):
253 | ResponseBase.__init__(self)
254 | self.message_list = []
255 |
256 | class DeleteMessageRequest(RequestBase):
257 | def __init__(self, queue_name, receipt_handle):
258 | RequestBase.__init__(self)
259 | self.queue_name = queue_name
260 | self.receipt_handle = receipt_handle
261 | self.method = "DELETE"
262 |
263 | class DeleteMessageResponse(ResponseBase):
264 | def __init__(self):
265 | ResponseBase.__init__(self)
266 |
267 | class BatchDeleteMessageRequest(RequestBase):
268 | def __init__(self, queue_name, receipt_handle_list):
269 | RequestBase.__init__(self)
270 | self.queue_name = queue_name
271 | self.receipt_handle_list = receipt_handle_list
272 | self.method = "DELETE"
273 |
274 | class BatchDeleteMessageResponse(ResponseBase):
275 | def __init__(self):
276 | ResponseBase.__init__(self)
277 |
278 | class ChangeMessageVisibilityRequest(RequestBase):
279 | def __init__(self, queue_name, receipt_handle, visibility_timeout):
280 | RequestBase.__init__(self)
281 | self.queue_name = queue_name
282 | self.receipt_handle = receipt_handle
283 | self.visibility_timeout = visibility_timeout
284 | self.method = "PUT"
285 |
286 | class ChangeMessageVisibilityResponse(ResponseBase):
287 | def __init__(self):
288 | ResponseBase.__init__(self)
289 | self.receipt_handle = ""
290 | self.next_visible_time = -1
291 |
292 | class CreateTopicRequest(RequestBase):
293 | def __init__(self, topic_name, maximum_message_size = -1, logging_enabled = None):
294 | RequestBase.__init__(self)
295 | self.topic_name = topic_name
296 | self.maximum_message_size = maximum_message_size
297 | self.logging_enabled = logging_enabled
298 | self.method = "PUT"
299 |
300 | class CreateTopicResponse(ResponseBase):
301 | def __init__(self):
302 | ResponseBase.__init__(self)
303 | self.topic_url = ""
304 |
305 | class DeleteTopicRequest(RequestBase):
306 | def __init__(self, topic_name):
307 | RequestBase.__init__(self)
308 | self.topic_name = topic_name
309 | self.method = "DELETE"
310 |
311 | class DeleteTopicResponse(ResponseBase):
312 | def __init__(self):
313 | ResponseBase.__init__(self)
314 |
315 | class ListTopicRequest(RequestBase):
316 | def __init__(self, prefix = "", ret_number = -1, marker = "", with_meta = False):
317 | RequestBase.__init__(self)
318 | self.prefix = prefix
319 | self.ret_number = ret_number
320 | self.marker = marker
321 | self.with_meta = with_meta
322 | self.method = "GET"
323 |
324 | class ListTopicResponse(ResponseBase):
325 | def __init__(self):
326 | ResponseBase.__init__(self)
327 | self.topicurl_list = []
328 | self.next_marker = ""
329 | self.topicmeta_list = []
330 |
331 | class SetTopicAttributesRequest(RequestBase):
332 | def __init__(self, topic_name, maximum_message_size = -1, logging_enabled = None):
333 | RequestBase.__init__(self)
334 | self.topic_name = topic_name
335 | self.maximum_message_size = maximum_message_size
336 | self.logging_enabled = logging_enabled
337 | self.method = "PUT"
338 |
339 | class SetTopicAttributesResponse(ResponseBase):
340 | def __init__(self):
341 | ResponseBase.__init__(self)
342 |
343 | class GetTopicAttributesRequest(RequestBase):
344 | def __init__(self, topic_name):
345 | RequestBase.__init__(self)
346 | self.topic_name = topic_name
347 | self.method = "GET"
348 |
349 | class GetTopicAttributesResponse(ResponseBase):
350 | def __init__(self):
351 | ResponseBase.__init__(self)
352 | self.message_count = -1
353 | self.create_time = -1
354 | self.last_modify_time = -1
355 | self.maximum_message_size = -1
356 | self.message_retention_period = -1
357 | self.topic_name = ""
358 | self.logging_enabled = None
359 |
360 | class PublishMessageRequest(RequestBase):
361 | def __init__(self, topic_name, message_body, message_tag="", direct_mail=None, direct_sms=None):
362 | RequestBase.__init__(self)
363 | self.topic_name = topic_name
364 | self.message_body = message_body
365 | self.message_tag = message_tag
366 | self.direct_mail = direct_mail
367 | self.direct_sms = direct_sms
368 | self.method = "POST"
369 |
370 | class PublishMessageResponse(ResponseBase):
371 | def __init__(self):
372 | ResponseBase.__init__(self)
373 | self.message_id = ""
374 | self.message_body_md5 = ""
375 |
376 | class SubscribeRequest(RequestBase):
377 | def __init__(self, topic_name, subscription_name, endpoint, notify_strategy = "", notify_content_format = "", filter_tag = ""):
378 | RequestBase.__init__(self)
379 | self.topic_name = topic_name
380 | self.subscription_name = subscription_name
381 | self.endpoint = endpoint
382 | self.filter_tag = filter_tag
383 | self.notify_strategy = notify_strategy
384 | self.notify_content_format = notify_content_format
385 | self.method = "PUT"
386 |
387 | class SubscribeResponse(ResponseBase):
388 | def __init__(self):
389 | ResponseBase.__init__(self)
390 | self.subscription_url = ""
391 |
392 | class UnsubscribeRequest(RequestBase):
393 | def __init__(self, topic_name, subscription_name):
394 | RequestBase.__init__(self)
395 | self.topic_name = topic_name
396 | self.subscription_name = subscription_name
397 | self.method = "DELETE"
398 |
399 | class UnsubscribeResponse(ResponseBase):
400 | def __init__(self):
401 | ResponseBase.__init__(self)
402 |
403 | class ListSubscriptionByTopicRequest(RequestBase):
404 | def __init__(self, topic_name, prefix = "", ret_number = -1, marker = ""):
405 | RequestBase.__init__(self)
406 | self.topic_name = topic_name
407 | self.prefix = prefix
408 | self.ret_number = ret_number
409 | self.marker = marker
410 | self.method = "GET"
411 |
412 | class ListSubscriptionByTopicResponse(ResponseBase):
413 | def __init__(self):
414 | ResponseBase.__init__(self)
415 | self.subscriptionurl_list = []
416 | self.next_marker = ""
417 | self.subscriptionmeta_list = []
418 |
419 | class SetSubscriptionAttributesRequest(RequestBase):
420 | def __init__(self, topic_name, subscription_name, endpoint = "", notify_strategy = "", notify_content_format = "", filter_tag = ""):
421 | RequestBase.__init__(self)
422 | self.topic_name = topic_name
423 | self.subscription_name = subscription_name
424 | self.endpoint = endpoint
425 | self.filter_tag = filter_tag
426 | self.notify_strategy = notify_strategy
427 | self.notify_content_format = notify_content_format
428 | self.method = "PUT"
429 |
430 | class SetSubscriptionAttributesResponse(ResponseBase):
431 | def __init__(self):
432 | ResponseBase.__init__(self)
433 |
434 | class GetSubscriptionAttributesRequest(RequestBase):
435 | def __init__(self, topic_name, subscription_name):
436 | RequestBase.__init__(self)
437 | self.topic_name = topic_name
438 | self.subscription_name = subscription_name
439 | self.method = "GET"
440 |
441 | class GetSubscriptionAttributesResponse(ResponseBase):
442 | def __init__(self):
443 | ResponseBase.__init__(self)
444 | self.topic_owner = ""
445 | self.topic_name = ""
446 | self.subscription_name = ""
447 | self.endpoint = ""
448 | self.filter_tag = ""
449 | self.notify_strategy = ""
450 | self.notify_content_format = ""
451 | self.create_time = -1
452 | self.last_modify_time = -1
453 |
454 | class OpenServiceRequest(RequestBase):
455 | def __init__(self):
456 | RequestBase.__init__(self)
457 | self.method = "POST"
458 |
459 |
460 | class OpenServiceResponse(ResponseBase):
461 | def __init__(self):
462 | ResponseBase.__init__(self)
463 | self.oder_id = ""
--------------------------------------------------------------------------------
/mns/mns_tool.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | import os
11 | import sys
12 | import string
13 | import types
14 | import logging
15 | import logging.handlers
16 | from .mns_exception import *
17 |
18 | METHODS = ["PUT", "POST", "GET", "DELETE"]
19 | PERMISSION_ACTIONS = ["setqueueattributes", "getqueueattributes", "sendmessage", "receivemessage", "deletemessage", "peekmessage", "changevisibility"]
20 |
21 | class MNSLogger:
22 | @staticmethod
23 | def get_logger(log_name=None, log_file=None, log_level=logging.INFO):
24 | if log_name is None:
25 | log_name = "mns_python_sdk"
26 | if log_file is None:
27 | log_file = os.path.join(os.path.split(os.path.realpath(__file__))[0], "mns_python_sdk.log")
28 | logger = logging.getLogger(log_name)
29 | if logger.handlers == []:
30 | fileHandler = logging.handlers.RotatingFileHandler(log_file, maxBytes=10*1024*1024)
31 | formatter = logging.Formatter('[%(asctime)s] [%(name)s] [%(levelname)s] [%(filename)s:%(lineno)d] [%(thread)d] %(message)s', '%Y-%m-%d %H:%M:%S')
32 | fileHandler.setFormatter(formatter)
33 | logger.addHandler(fileHandler)
34 | MNSLogger.validate_loglevel(log_level)
35 | logger.setLevel(log_level)
36 | return logger
37 |
38 | @staticmethod
39 | def validate_loglevel(log_level):
40 | log_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]
41 | if log_level not in log_levels:
42 | raise MNSClientParameterException("LogLevelInvalid", "Bad value: '%s', expect levels: '%s'." % \
43 | (log_level, ','.join([str(item) for item in log_levels])))
44 |
45 | class ValidatorBase:
46 | @staticmethod
47 | def validate(req):
48 | pass
49 |
50 | @staticmethod
51 | def type_validate(item, valid_type, param_name=None, req_id=None):
52 | if not (type(item) is valid_type):
53 | if param_name is None:
54 | raise MNSClientParameterException("TypeInvalid", "Bad type: '%s', '%s' expect type '%s'." % (type(item), item, valid_type), req_id)
55 | else:
56 | raise MNSClientParameterException("TypeInvalid", "Param '%s' in bad type: '%s', '%s' expect type '%s'." % (param_name, type(item), item, valid_type), req_id)
57 |
58 | @staticmethod
59 | def is_str(item, param_name=None, req_id=None):
60 | #if not isinstance(item, unicode):
61 | # if param_name is None:
62 | # raise MNSClientParameterException("TypeInvalid", "Bad type: '%s', '%s' expect basestring." % (type(item), item), req_id)
63 | # else:
64 | # raise MNSClientParameterException("TypeInvalid", "Param '%s' in bad type: '%s', '%s' expect basestring." % (param_name, type(item), item), req_id)
65 | return
66 |
67 | @staticmethod
68 | def marker_validate(req):
69 | ValidatorBase.is_str(req.marker, req_id=req.request_id)
70 |
71 | @staticmethod
72 | def retnumber_validate(req):
73 | #ValidatorBase.type_validate(req.ret_number, types.IntType, req_id=req.request_id)
74 | ValidatorBase.type_validate(req.ret_number, int, req_id=req.request_id)
75 | if (req.ret_number != -1 and req.ret_number <= 0 ):
76 | raise MNSClientParameterException("HeaderInvalid", "Bad value: '%s', x-mns-number should larger than 0." % req.ret_number, req.request_id)
77 |
78 | @staticmethod
79 | def name_validate(name, nameType, req_id=None):
80 | #type
81 | ValidatorBase.is_str(name, req_id=req_id)
82 |
83 | #length
84 | if len(name) < 1:
85 | raise MNSClientParameterException("QueueNameInvalid", "Bad value: '%s', the length of %s should larger than 1." % (name, nameType), req_id)
86 |
87 | @staticmethod
88 | def list_condition_validate(req):
89 | if req.prefix != "":
90 | ValidatorBase.name_validate(req.prefix, "prefix")
91 |
92 | ValidatorBase.marker_validate(req)
93 | ValidatorBase.retnumber_validate(req)
94 |
95 | class SetAccountAttributesValidator(ValidatorBase):
96 | @staticmethod
97 | def validate(req):
98 | #type
99 | if req.logging_bucket is not None:
100 | ValidatorBase.is_str(req.logging_bucket, req_id=req.request_id)
101 |
102 | class QueueValidator(ValidatorBase):
103 | @staticmethod
104 | def queue_validate(req):
105 | #type
106 | #ValidatorBase.type_validate(req.visibility_timeout, types.IntType, req_id=req.request_id)
107 | #ValidatorBase.type_validate(req.maximum_message_size, types.IntType, req_id=req.request_id)
108 | #ValidatorBase.type_validate(req.message_retention_period, types.IntType, req_id=req.request_id)
109 | #ValidatorBase.type_validate(req.delay_seconds, types.IntType, req_id=req.request_id)
110 | #ValidatorBase.type_validate(req.polling_wait_seconds, types.IntType, req_id=req.request_id)
111 | ValidatorBase.type_validate(req.visibility_timeout, int, req_id=req.request_id)
112 | ValidatorBase.type_validate(req.maximum_message_size, int, req_id=req.request_id)
113 | ValidatorBase.type_validate(req.message_retention_period, int, req_id=req.request_id)
114 | ValidatorBase.type_validate(req.delay_seconds, int, req_id=req.request_id)
115 | ValidatorBase.type_validate(req.polling_wait_seconds, int, req_id=req.request_id)
116 |
117 | #value
118 | if req.visibility_timeout != -1 and req.visibility_timeout <= 0:
119 | raise MNSClientParameterException("QueueAttrInvalid", "Bad value: '%d', visibility timeout should larger than 0." % req.visibility_timeout, req.request_id)
120 | if req.maximum_message_size != -1 and req.maximum_message_size <= 0:
121 | raise MNSClientParameterException("QueueAttrInvalid", "Bad value: '%d', maximum message size should larger than 0." % req.maximum_message_size, req.request_id)
122 | if req.message_retention_period != -1 and req.message_retention_period <= 0:
123 | raise MNSClientParameterException("QueueAttrInvalid", "Bad value: '%d', message retention period should larger than 0." % req.message_retention_period, req.request_id)
124 | if req.delay_seconds != -1 and req.delay_seconds < 0:
125 | raise MNSClientParameterException("QueueAttrInvalid", "Bad value: '%d', delay seconds should larger than 0." % req.delay_seconds, req.request_id)
126 | if req.polling_wait_seconds != -1 and req.polling_wait_seconds < 0:
127 | raise MNSClientParameterException("QueueAttrInvalid", "Bad value: '%d', polling wait seconds should larger than 0." % req.polling_wait_seconds, req.request_id)
128 | if req.logging_enabled != None and str(req.logging_enabled).lower() not in ("true", "false"):
129 | raise MNSClientParameterException("QueueAttrInvalid", "Bad value: '%s', logging enabled should be True/False." % req.logging_enabled, req.request_id)
130 |
131 | class MessageValidator(ValidatorBase):
132 | @staticmethod
133 | def sendmessage_attr_validate(req, req_id):
134 | #type
135 | ValidatorBase.is_str(req.message_body, None, req_id)
136 | ValidatorBase.type_validate(req.delay_seconds, int, None, req_id)
137 | ValidatorBase.type_validate(req.priority, int, None, req_id)
138 |
139 | #value
140 | if req.message_body == "":
141 | raise MNSClientParameterException("MessageBodyInvalid", "Bad value: '', message body should not be ''.", req_id)
142 |
143 | if req.delay_seconds != -1 and req.delay_seconds < 0:
144 | raise MNSClientParameterException("DelaySecondsInvalid", "Bad value: '%d', delay_seconds should larger than 0." % req.delay_seconds, req_id)
145 |
146 | if req.priority != -1 and req.priority < 0:
147 | raise MNSClientParameterException("PriorityInvalid", "Bad value: '%d', priority should larger than 0." % req.priority, req_id)
148 |
149 | @staticmethod
150 | def receiphandle_validate(receipt_handle, req_id):
151 | if (receipt_handle == ""):
152 | raise MNSClientParameterException("ReceiptHandleInvalid", "The receipt handle should not be null.", req_id)
153 |
154 | @staticmethod
155 | def waitseconds_validate(wait_seconds, req_id):
156 | if wait_seconds != -1 and wait_seconds < 0:
157 | raise MNSClientParameterException("WaitSecondsInvalid", "Bad value: '%d', wait_seconds should larger than 0." % wait_seconds, req_id)
158 |
159 | @staticmethod
160 | def batchsize_validate(batch_size, req_id):
161 | if batch_size != -1 and batch_size < 0:
162 | raise MNSClientParameterException("BatchSizeInvalid", "Bad value: '%d', batch_size should larger than 0." % batch_size, req_id)
163 |
164 | @staticmethod
165 | def publishmessage_attr_validate(req):
166 | #type
167 | ValidatorBase.is_str(req.message_body, "message_body", req_id=req.request_id)
168 | ValidatorBase.is_str(req.message_tag, "message_tag", req_id=req.request_id)
169 | if req.direct_mail is not None:
170 | ValidatorBase.is_str(req.direct_mail.account_name, "account_name of direct mail", req_id=req.request_id)
171 | ValidatorBase.is_str(req.direct_mail.subject, "subject of direct mail", req_id=req.request_id)
172 |
173 | #value
174 | if req.message_body == "":
175 | raise MNSClientParameterException("MessageBodyInvalid", "Bad value: '', message body should not be ''.", req.request_id)
176 | if len(req.message_tag) > 16:
177 | raise MNSClientParameterException("MessageTagInvalid", "The length of message tag should be between 1 and 16.", req.request_id)
178 |
179 | class CreateQueueValidator(QueueValidator):
180 | @staticmethod
181 | def validate(req):
182 | QueueValidator.validate(req)
183 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
184 | QueueValidator.queue_validate(req)
185 |
186 | class DeleteQueueValidator(QueueValidator):
187 | @staticmethod
188 | def validate(req):
189 | QueueValidator.validate(req)
190 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
191 |
192 | class ListQueueValidator(QueueValidator):
193 | @staticmethod
194 | def validate(req):
195 | QueueValidator.validate(req)
196 | QueueValidator.list_condition_validate(req)
197 |
198 | class SetQueueAttrValidator(QueueValidator):
199 | @staticmethod
200 | def validate(req):
201 | QueueValidator.validate(req)
202 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
203 | QueueValidator.queue_validate(req)
204 |
205 | class GetQueueAttrValidator(QueueValidator):
206 | @staticmethod
207 | def validate(req):
208 | QueueValidator.validate(req)
209 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
210 |
211 | class SendMessageValidator(MessageValidator):
212 | @staticmethod
213 | def validate(req):
214 | MessageValidator.validate(req)
215 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
216 | MessageValidator.sendmessage_attr_validate(req, req.request_id)
217 |
218 | class BatchSendMessageValidator(MessageValidator):
219 | @staticmethod
220 | def validate(req):
221 | MessageValidator.validate(req)
222 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
223 | for entry in req.message_list:
224 | MessageValidator.sendmessage_attr_validate(entry, req.request_id)
225 |
226 | class ReceiveMessageValidator(MessageValidator):
227 | @staticmethod
228 | def validate(req):
229 | MessageValidator.validate(req)
230 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
231 | MessageValidator.waitseconds_validate(req.wait_seconds, req.request_id)
232 |
233 | class BatchReceiveMessageValidator(MessageValidator):
234 | @staticmethod
235 | def validate(req):
236 | MessageValidator.validate(req)
237 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
238 | MessageValidator.batchsize_validate(req.batch_size, req.request_id)
239 | MessageValidator.waitseconds_validate(req.wait_seconds, req.request_id)
240 |
241 | class DeleteMessageValidator(MessageValidator):
242 | @staticmethod
243 | def validate(req):
244 | MessageValidator.validate(req)
245 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
246 | MessageValidator.receiphandle_validate(req.receipt_handle, req.request_id)
247 |
248 | class BatchDeleteMessageValidator(MessageValidator):
249 | @staticmethod
250 | def validate(req):
251 | MessageValidator.validate(req)
252 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
253 | for receipt_handle in req.receipt_handle_list:
254 | MessageValidator.receiphandle_validate(receipt_handle, req.request_id)
255 |
256 | class PeekMessageValidator(MessageValidator):
257 | @staticmethod
258 | def validate(req):
259 | MessageValidator.validate(req)
260 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
261 |
262 | class BatchPeekMessageValidator(MessageValidator):
263 | @staticmethod
264 | def validate(req):
265 | MessageValidator.validate(req)
266 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
267 | MessageValidator.batchsize_validate(req.batch_size, req.request_id)
268 |
269 | class ChangeMsgVisValidator(MessageValidator):
270 | @staticmethod
271 | def validate(req):
272 | MessageValidator.validate(req)
273 | ValidatorBase.name_validate(req.queue_name, "queue_name", req.request_id)
274 | MessageValidator.receiphandle_validate(req.receipt_handle, req.request_id)
275 | if (req.visibility_timeout < 0 or req.visibility_timeout > 43200 ):
276 | raise MNSClientParameterException("VisibilityTimeoutInvalid", "Bad value: '%d', visibility timeout should between 0 and 43200." % req.visibility_timeout, req.request_id)
277 |
278 | class TopicValidator(ValidatorBase):
279 | @staticmethod
280 | def topic_validate(req):
281 | #type
282 | ValidatorBase.type_validate(req.maximum_message_size, int, "maximum_message_size", req_id=req.request_id)
283 |
284 | #value
285 | if req.maximum_message_size != -1 and req.maximum_message_size <= 0:
286 | raise MNSClientParameterException("TopicAttrInvalid", "Bad value: '%s', maximum message size should larger than 0." % req.maximum_message_size, req.request_id)
287 | if req.logging_enabled != None and str(req.logging_enabled).lower() not in ("true", "false"):
288 | raise MNSClientParameterException("TopicAttrInvalid", "Bad value: '%s', logging enabled should be True/False." % req.logging_enabled, req.request_id)
289 |
290 | class CreateTopicValidator(TopicValidator):
291 | @staticmethod
292 | def validate(req):
293 | TopicValidator.validate(req)
294 | ValidatorBase.name_validate(req.topic_name, "topic_name", req.request_id)
295 | TopicValidator.topic_validate(req)
296 |
297 | class DeleteTopicValidator(TopicValidator):
298 | @staticmethod
299 | def validate(req):
300 | TopicValidator.validate(req)
301 | ValidatorBase.name_validate(req.topic_name, "topic_name", req.request_id)
302 |
303 | class ListTopicValidator(TopicValidator):
304 | @staticmethod
305 | def validate(req):
306 | TopicValidator.validate(req)
307 | TopicValidator.list_condition_validate(req)
308 |
309 | class SetTopicAttrValidator(TopicValidator):
310 | @staticmethod
311 | def validate(req):
312 | TopicValidator.validate(req)
313 | ValidatorBase.name_validate(req.topic_name, "topic_name", req.request_id)
314 | TopicValidator.topic_validate(req)
315 |
316 | class GetTopicAttrValidator(TopicValidator):
317 | @staticmethod
318 | def validate(req):
319 | TopicValidator.validate(req)
320 | ValidatorBase.name_validate(req.topic_name, "topic_name", req.request_id)
321 |
322 | class PublishMessageValidator(MessageValidator):
323 | @staticmethod
324 | def validate(req):
325 | MessageValidator.validate(req)
326 | ValidatorBase.name_validate(req.topic_name, "topic_name", req.request_id)
327 | MessageValidator.publishmessage_attr_validate(req)
328 |
329 | class SubscriptionValidator(TopicValidator):
330 | @staticmethod
331 | def subscription_validate(req):
332 | TopicValidator.is_str(req.endpoint, "endpoint", req_id=req.request_id)
333 | TopicValidator.is_str(req.notify_strategy, "notify_strategy", req_id=req.request_id)
334 | TopicValidator.is_str(req.filter_tag, "filter_tag", req_id=req.request_id)
335 | TopicValidator.is_str(req.notify_content_format, "notify_content_format", req_id=req.request_id)
336 |
337 | @staticmethod
338 | def filter_tag_validate(filter_tag, req_id):
339 | if len(filter_tag) > 16:
340 | raise MNSClientParameterException("FilterTagInvalid", "Bad value: '%s', The length of filter tag should be between 1 and 16." % (filter_tag))
341 |
342 | class SubscribeValidator(SubscriptionValidator):
343 | @staticmethod
344 | def validate(req):
345 | SubscriptionValidator.validate(req)
346 | ValidatorBase.name_validate(req.topic_name, "topic_name", req.request_id)
347 | ValidatorBase.name_validate(req.subscription_name, "subscription_name", req.request_id)
348 | SubscriptionValidator.subscription_validate(req)
349 | SubscriptionValidator.filter_tag_validate(req.filter_tag, req.request_id)
350 |
351 | class UnsubscribeValidator(SubscriptionValidator):
352 | @staticmethod
353 | def validate(req):
354 | SubscriptionValidator.validate(req)
355 | ValidatorBase.name_validate(req.topic_name, "topic_name", req.request_id)
356 | ValidatorBase.name_validate(req.subscription_name, "subscription_name", req.request_id)
357 |
358 | class ListSubscriptionByTopicValidator(SubscriptionValidator):
359 | @staticmethod
360 | def validate(req):
361 | SubscriptionValidator.validate(req)
362 | SubscriptionValidator.list_condition_validate(req)
363 |
364 | class SetSubscriptionAttrValidator(SubscriptionValidator):
365 | @staticmethod
366 | def validate(req):
367 | SubscriptionValidator.validate(req)
368 | ValidatorBase.name_validate(req.topic_name, "topic_name", req.request_id)
369 | ValidatorBase.name_validate(req.subscription_name, "subscription_name", req.request_id)
370 | SubscriptionValidator.subscription_validate(req)
371 |
372 | class GetSubscriptionAttrValidator(SubscriptionValidator):
373 | @staticmethod
374 | def validate(req):
375 | SubscriptionValidator.validate(req)
376 | ValidatorBase.name_validate(req.topic_name, "topic_name", req.request_id)
377 | ValidatorBase.name_validate(req.subscription_name, "subscription_name", req.request_id)
378 |
--------------------------------------------------------------------------------
/sample.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #coding=utf8
3 | # Copyright (C) 2015, Alibaba Cloud Computing
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | import sys
11 | import os
12 | import time
13 | from mns.account import Account
14 | from mns.queue import *
15 | from mns.topic import *
16 | from mns.subscription import *
17 | try:
18 | import configparser as ConfigParser
19 | except ImportError:
20 | import ConfigParser as ConfigParser
21 |
22 | cfgFN = "sample.cfg"
23 | required_ops = [("Base", "Endpoint")]
24 |
25 | parser = ConfigParser.ConfigParser()
26 | parser.read(cfgFN)
27 | for sec, op in required_ops:
28 | if not parser.has_option(sec, op):
29 | sys.stderr.write("ERROR: need (%s, %s) in %s.\n" % (sec, op, cfgFN))
30 | sys.stderr.write("Read README to get help inforamtion.\n")
31 | sys.exit(1)
32 |
33 | #获取配置信息
34 | ## AccessKeyId 阿里云官网获取
35 | ## AccessKeySecret 阿里云官网获取
36 | ## Endpoint 阿里云消息和通知服务官网获取, Example: http://$AccountId.mns.cn-hangzhou.aliyuncs.com
37 | ## WARNING: Please do not hard code your accessId and accesskey in next line.(more information see README)
38 | accessKeyId = os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
39 | accessKeySecret = os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
40 | securityToken = os.getenv("ALIBABA_CLOUD_ACCESS_SECURITY_TOKEN") or ""
41 | endpoint = parser.get("Base", "Endpoint")
42 |
43 |
44 | #初始化my_account
45 | my_account = Account(endpoint, accessKeyId, accessKeySecret, securityToken)
46 |
47 | ##############Queue 相关操作#####################
48 | my_queue = my_account.get_queue("MyQueue-%s" % time.strftime("%y%m%d-%H%M%S", time.localtime()))
49 |
50 | #创建队列
51 | ## message被receive后,持续不可消费的时间 100秒
52 | ## message body的最大长度 10240Byte
53 | ## message最长存活时间 3600秒
54 | ## 新message可消费的默认延迟时间 10秒
55 | ## receive message时,长轮询时间 20秒
56 | queue_meta = QueueMeta()
57 | queue_meta.set_visibilitytimeout(100)
58 | queue_meta.set_maximum_message_size(10240)
59 | queue_meta.set_message_retention_period(3600)
60 | queue_meta.set_delay_seconds(10)
61 | queue_meta.set_polling_wait_seconds(20)
62 | queue_meta.set_logging_enabled(True)
63 | try:
64 | queue_url = my_queue.create(queue_meta)
65 | sys.stdout.write("Create Queue Succeed!\nQueueURL:%s\n\n" % queue_url)
66 | except MNSExceptionBase as e:
67 | sys.stderr.write("Create Queue Fail!\nException:%s\n\n" % e)
68 | sys.exit(1)
69 |
70 | #修改队列属性
71 | ## message被receive后,持续不可消费的时间 50秒
72 | ## message body的最大长度 5120Byte
73 | ## message最长存活时间 1800秒
74 | ## 新message可消费的默认延迟时间 5秒
75 | ## receive message时,长轮询时间 10秒
76 | queue_meta = QueueMeta()
77 | queue_meta.set_visibilitytimeout(50)
78 | queue_meta.set_maximum_message_size(5120)
79 | queue_meta.set_message_retention_period(1800)
80 | queue_meta.set_delay_seconds(5)
81 | queue_meta.set_polling_wait_seconds(10)
82 | try:
83 | queue_url = my_queue.set_attributes(queue_meta)
84 | sys.stdout.write("Set Queue Attributes Succeed!\n\n")
85 | except MNSExceptionBase as e:
86 | sys.stderr.write("Set Queue Attributes Fail!\nException:%s\n\n" % e)
87 | sys.exit(1)
88 |
89 | #获取队列属性
90 | ## 除可设置属性外,返回如下属性:
91 | ## ActiveMessages: 可消费消息数,近似值
92 | ## InactiveMessages: 正在被消费的消息数,近似值
93 | ## DelayMessages: 延迟消息数,近似值
94 | ## CreateTime: queue创建时间,单位:秒
95 | ## LastModifyTime: 修改queue属性的最近时间,单位:秒
96 | try:
97 | queue_meta = my_queue.get_attributes()
98 | sys.stdout.write("Get Queue Attributes Succeed! \
99 | \nQueueName: %s\nVisibilityTimeout: %s \
100 | \nMaximumMessageSize: %s\nDelaySeconds: %s \
101 | \nPollingWaitSeconds: %s\nActiveMessages: %s \
102 | \nInactiveMessages: %s\nDelayMessages: %s \
103 | \nCreateTime: %s\nLastModifyTime: %s\n\n" %
104 | (queue_meta.queue_name, queue_meta.visibility_timeout,
105 | queue_meta.maximum_message_size, queue_meta.delay_seconds,
106 | queue_meta.polling_wait_seconds, queue_meta.active_messages,
107 | queue_meta.inactive_messages, queue_meta.delay_messages,
108 | queue_meta.create_time, queue_meta.last_modify_time))
109 | except MNSExceptionBase as e:
110 | sys.stderr.write("Get Queue Attributes Fail!\nException:%s\n\n" % e)
111 | sys.exit(1)
112 |
113 | #列出所有队列
114 | ## prefix 指定queue name前缀
115 | ## ret_number 单次list_queue最大返回队列个数
116 | ## marker list_queue的开始位置; 当一次list queue不能列出所有队列时,返回的next_marker作为下一次list queue的marker参数
117 | try:
118 | prefix = u""
119 | ret_number = 10
120 | marker = u""
121 | total_qcount = 0
122 | while(True):
123 | queue_url_list, next_marker = my_account.list_queue(prefix, ret_number, marker)
124 | total_qcount += len(queue_url_list)
125 | for queue_url in queue_url_list:
126 | sys.stdout.write("QueueURL:%s\n" % queue_url)
127 | if(next_marker == ""):
128 | break
129 | marker = next_marker
130 | sys.stdout.write("List Queue Succeed! Total Queue Count:%s!\n\n" % total_qcount)
131 | except MNSExceptionBase as e:
132 | sys.stderr.write("List Queue Fail!\nException:%s\n\n" % e)
133 | sys.exit(1)
134 |
135 | #发送消息
136 | ## set_delayseconds 设置消息的延迟时间,单位:秒
137 | ## set_priority 设置消息的优先级
138 | ## 返回如下属性:
139 | ## MessageId 消息编号
140 | ## MessageBodyMd5 消息正文的MD5值
141 | msg_body = "I am test Message."
142 | message = Message(msg_body)
143 | message.set_delayseconds(0)
144 | message.set_priority(10)
145 | try:
146 | send_msg = my_queue.send_message(message)
147 | sys.stdout.write("Send Message Succeed.\nMessageBody:%s\nMessageId:%s\nMessageBodyMd5:%s\n\n" % (msg_body, send_msg.message_id, send_msg.message_body_md5))
148 | except MNSExceptionBase as e:
149 | sys.stderr.write("Send Message Fail!\nException:%s\n\n" % e)
150 | sys.exit(1)
151 |
152 | # 在消息延迟之后开始查看,否则查看不到消息
153 | time.sleep(6)
154 |
155 | #查看消息
156 | ## 返回如下属性:
157 | ## MessageId 消息编号
158 | ## MessageBodyMD5 消息正文的MD5值
159 | ## MessageBody 消息正文
160 | ## DequeueCount 消息被消费的次数
161 | ## EnqueueTime 消息发送到队列的时间,单位:毫秒
162 | ## FirstDequeueTime 消息第一次被消费的时间,单位:毫秒
163 | ## Priority 消息的优先级
164 | ## peek_message 返回字节串,peek_message_with_str_body 返回字符串
165 | try:
166 | peek_msg = my_queue.peek_message_with_str_body()
167 | sys.stdout.write("Peek Message Succeed! \
168 | \nMessageId: %s\nMessageBodyMD5: %s \
169 | \nMessageBody: %s\nDequeueCount: %s \
170 | \nEnqueueTime: %s\nFirstDequeueTime: %s \
171 | \nPriority: %s\n\n" %
172 | (peek_msg.message_id, peek_msg.message_body_md5,
173 | peek_msg.message_body, peek_msg.dequeue_count,
174 | peek_msg.enqueue_time, peek_msg.first_dequeue_time,
175 | peek_msg.priority))
176 | except MNSExceptionBase as e:
177 | sys.stderr.write("Peek Message Fail!\nException:%s\n\n" % e)
178 |
179 | #消费消息
180 | ## wait_seconds 指定长轮询时间,单位:秒
181 | ## 返回属性和查看消息基本相同,增加NextVisibleTime和ReceiptHandle
182 | ## NextVisibleTime 消息下次可被消费的时间,单位:毫秒
183 | ## ReceiptHandle 本次消费消息产生的临时句柄,用于删除或修改处于Inactive的消息,NextVisibleTime之前有效
184 | ## receive_message 返回字节串,receive_message_with_str_body 返回字符串
185 | recv_msg = None
186 | try:
187 | wait_seconds = 10
188 | recv_msg = my_queue.receive_message_with_str_body(wait_seconds)
189 | sys.stdout.write("Receive Message Succeed! \
190 | \nMessageId: %s\nMessageBodyMD5: %s \
191 | \nMessageBody: %s\nDequeueCount: %s \
192 | \nEnqueueTime: %s\nFirstDequeueTime: %s \
193 | \nPriority: %s\nNextVisibleTime: %s \
194 | \nReceiptHandle: %s\n\n" %
195 | (recv_msg.message_id, recv_msg.message_body_md5,
196 | recv_msg.message_body, recv_msg.dequeue_count,
197 | recv_msg.enqueue_time, recv_msg.first_dequeue_time,
198 | recv_msg.priority, recv_msg.next_visible_time,
199 | recv_msg.receipt_handle))
200 | except MNSExceptionBase as e:
201 | sys.stderr.write("Receive Message Fail!\nException:%s\n\n" % e)
202 |
203 | #修改消息下次可消费时间
204 | ## 参数1:recv_msg.receitp_handle 消费消息返回的临时句柄
205 | ## 参数2:35 将消息下次可被消费时间修改为:now+35秒
206 | ## 返回属性:
207 | ## ReceiptHandle 新的临时句柄,下次删除或修改这条消息时使用
208 | ## NextVisibleTime 消息下次可被消费的时间,单位:毫秒
209 | change_msg_vis = None
210 | if recv_msg is not None:
211 | try:
212 | change_msg_vis = my_queue.change_message_visibility(recv_msg.receipt_handle, 35)
213 | sys.stdout.write("Change Message Visibility Succeed!\nReceiptHandle:%s\nNextVisibleTime:%s\n\n" %
214 | (change_msg_vis.receipt_handle, change_msg_vis.next_visible_time))
215 | except MNSExceptionBase as e:
216 | sys.stderr.write("Change Message Visibility Fail!\nException:%s\n\n" % e)
217 | sys.exit(1)
218 |
219 | #删除消息
220 | ## change_msg_vis.receipt_handle 最近操作该消息的临时句柄
221 | if change_msg_vis is not None:
222 | try:
223 | my_queue.delete_message(change_msg_vis.receipt_handle)
224 | sys.stdout.write("Delete Message Succeed.\n\n")
225 | except MNSExceptionBase as e:
226 | sys.stderr.write("Delete Message Fail!\nException:%s\n\n" % e)
227 | sys.exit(1)
228 |
229 | #批量发送消息,消息条数的限制请参考官网API文档
230 | ## 返回多条消息的MessageId和MessageBodyMD5
231 | messages = []
232 | msg_cnt = 16
233 | for i in range(msg_cnt):
234 | msg = Message("I am test Message %s." % i)
235 | msg.set_delayseconds(0)
236 | msg.set_priority(6)
237 | messages.append(msg)
238 | try:
239 | send_msgs = my_queue.batch_send_message(messages)
240 | sys.stdout.write("Batch Send Message Succeed.\n")
241 | for msg in send_msgs:
242 | sys.stdout.write("MessageId:%s\nMessageBodyMd5:%s\n\n" % (msg.message_id, msg.message_body_md5))
243 | except MNSExceptionBase as e:
244 | sys.stderr.write("Batch Send Message Fail!\nException:%s\n\n" % e)
245 | sys.exit(1)
246 |
247 | time.sleep(6)
248 |
249 | #批量查看消息
250 | ## batch_size 指定批量获取的消息数
251 | ## 返回多条消息的属性,每条消息属性和peek_message相同
252 | ## batch_peek_message 返回字节串,batch_peek_message_with_str_body 返回字符串
253 | try:
254 | batch_size = 3
255 | peek_msgs = my_queue.batch_peek_message_with_str_body(batch_size)
256 | sys.stdout.write("Batch Peek Message Succeed.\n")
257 | for msg in peek_msgs:
258 | sys.stdout.write("MessageId: %s\nMessageBodyMD5: %s \
259 | \nMessageBody: %s\nDequeueCount: %s \
260 | \nEnqueueTime: %s\nFirstDequeueTime: %s \
261 | \nPriority: %s\n\n" %
262 | (msg.message_id, msg.message_body_md5,
263 | msg.message_body, msg.dequeue_count,
264 | msg.enqueue_time, msg.first_dequeue_time,
265 | msg.priority))
266 | except MNSExceptionBase as e:
267 | sys.stderr.write("Batch Peek Message Fail!\nException:%s\n\n" % e)
268 |
269 | #批量消费消息
270 | ## batch_size 指定批量获取的消息数
271 | ## wait_seconds 指定长轮询时间,单位:秒
272 | ## 返回多条消息的属性,每条消息属性和receive_message相同
273 | ## batch_receive_message 返回字节串,batch_receive_message_with_str_body 返回字符串
274 | recv_msgs = []
275 | try:
276 | batch_size = 3
277 | wait_seconds = 10
278 | recv_msgs = my_queue.batch_receive_message_with_str_body(batch_size, wait_seconds)
279 | sys.stdout.write("Batch Receive Message Succeed.\n")
280 | for msg in recv_msgs:
281 | sys.stdout.write("MessageId: %s\nMessageBodyMD5: %s \
282 | \nMessageBody: %s\nDequeueCount: %s \
283 | \nEnqueueTime: %s\nFirstDequeueTime: %s \
284 | \nPriority: %s\nNextVisibleTime: %s \
285 | \nReceiptHandle: %s\n\n" %
286 | (msg.message_id, msg.message_body_md5,
287 | msg.message_body, msg.dequeue_count,
288 | msg.enqueue_time, msg.first_dequeue_time,
289 | msg.priority, msg.next_visible_time,
290 | msg.receipt_handle))
291 | except MNSExceptionBase as e:
292 | sys.stderr.write("Batch Receive Message Fail!\nException:%s\n\n" % e)
293 |
294 | #批量删除消息
295 | ## receipt_handle_list batch_receive_message返回的多个receipt handle
296 | try:
297 | receipt_handle_list = [msg.receipt_handle for msg in recv_msgs]
298 | my_queue.batch_delete_message(receipt_handle_list)
299 | sys.stdout.write("Batch Delete Message Succeed.\n")
300 | except MNSExceptionBase as e:
301 | sys.stderr.write("Batch Delete Message Fail!\nException:%s\n\n" % e)
302 | sys.exit(1)
303 |
304 | #删除队列
305 | try:
306 | my_queue.delete()
307 | sys.stdout.write("Delete Queue Succeed!\n\n")
308 | except MNSExceptionBase as e:
309 | sys.stderr.write("Delete Queue Fail!\nException:%s\n\n" % e)
310 | sys.exit(1)
311 |
312 | ##############Topic 相关操作#####################
313 | my_topic = my_account.get_topic("MyTopic-%s" % time.strftime("%y%m%d-%H%M%S", time.localtime()))
314 |
315 | #创建主题
316 | ## maximum_message_size 消息的最大长度 10240Byte
317 | topic_meta = TopicMeta()
318 | topic_meta.set_maximum_message_size(10240)
319 | try:
320 | topic_url = my_topic.create(topic_meta)
321 | sys.stdout.write("Create Topic Succeed!\nTopicURL:%s\n\n" % topic_url)
322 | except MNSExceptionBase as e:
323 | sys.stderr.write("Create Topic Fail!\nException:%s\n\n" % e)
324 | sys.exit(1)
325 |
326 | #修改主题属性
327 | ## maximum_message_size 消息的最大长度 6144Byte
328 | topic_meta = TopicMeta()
329 | topic_meta.set_maximum_message_size(6144)
330 | topic_meta.set_logging_enabled(True)
331 | try:
332 | my_topic.set_attributes(topic_meta)
333 | sys.stdout.write("Set Topic Attributes Succeed!\n\n")
334 | except MNSExceptionBase as e:
335 | sys.stderr.write("Set Topic Attributes Fail!\nException:%s\n\n" % e)
336 | sys.exit(1)
337 |
338 | #获取主题属性
339 | ## 除可设置属性外,返回如下属性:
340 | ## MessageCount: 主题中的消息条数
341 | ## MessageRetentionPeriod: 消息的最长存活时间,单位:秒
342 | ## CreateTime: 主题的创建时间,单位:秒
343 | ## LastModifyTime: 最后修改主题属性的时间,单位:秒
344 | try:
345 | topic_meta = my_topic.get_attributes()
346 | sys.stdout.write("Get Topic Attributes Succeed!\n%s" % topic_meta)
347 | except MNSExceptionBase as e:
348 | sys.stderr.write("Get Topic Attributes Fail!\nException:%s\n\n" % e)
349 | sys.exit(1)
350 |
351 | #列出所有主题
352 | ## prefix 指定主题名称的前缀
353 | ## ret_number 单次list_topic最大返回主题个数
354 | ## marker list_topic的开始位置;当一次list topic不能列出所有主题时,返回的next_marker作为下一次list topic的marker参数
355 | try:
356 | prefix = ""
357 | ret_number = 10
358 | marker = ""
359 | total_count = 0
360 | while(True):
361 | topic_url_list, next_marker = my_account.list_topic(prefix, ret_number, marker)
362 | total_count += len(topic_url_list)
363 | for topic_url in topic_url_list:
364 | sys.stdout.write("TopicURL:%s\n" % topic_url)
365 | if (next_marker == ""):
366 | break
367 | marker = next_marker
368 | sys.stdout.write("List Topic Succeed! Total Topic Count:%s\n\n" % total_count)
369 | except MNSExceptionBase as e:
370 | sys.stderr.write("List Topic Fail!\nException:%s\n\n" % e)
371 | sys.exit(1)
372 |
373 | #创建订阅
374 | ## endpoint 接收端地址
375 | ## filter_tag 消息过滤的标签,不能超过16个字符
376 | ## notify_strategy 向Endpoint推送消息错误时的重试策略
377 | ## notify_content_format 向Endpoint推送的消息内容格式
378 | endpoint = "http://www.baidu.com"
379 | filter_tag = "important"
380 | sub_meta = SubscriptionMeta(endpoint, SubscriptionNotifyStrategy.BACKOFF, SubscriptionNotifyContentFormat.XML, filter_tag)
381 | my_sub = my_topic.get_subscription("MySubscription-%s" % time.strftime("%y%m%d-%H%M%S", time.localtime()))
382 | try:
383 | sub_url = my_sub.subscribe(sub_meta)
384 | sys.stdout.write("Subscribe Topic Succeed!\nSubscriptionURL:%s\n\n" % sub_url)
385 | except MNSExceptionBase as e:
386 | sys.stderr.write("Subscribe Topic Fail!\nException:%s\n\n" % e)
387 | sys.exit(1)
388 |
389 | #修改订阅属性
390 | ## notify_strategy 向Endpoint推送消息错误时的重试策略
391 | sub_meta = SubscriptionMeta(notify_strategy=SubscriptionNotifyStrategy.EXPONENTIAL)
392 | try:
393 | my_sub.set_attributes(sub_meta)
394 | sys.stdout.write("Set Subscriptoin Attributes Succeed!\n\n")
395 | except MNSExceptionBase as e:
396 | sys.stderr.write("Set Subscriptoin Attributes Fail!\nExceptoin:%s\n\n" % e)
397 | sys.exit(1)
398 |
399 | #获取订阅属性
400 | ## 除可设置属性外,返回如下属性:
401 | ## CreateTime 订阅创建时间,单位:秒
402 | ## LastModifyTime 最后修改订阅属性的时间,单位:秒
403 | try:
404 | sub_meta = my_sub.get_attributes()
405 | sys.stdout.write("Get Subscription Attributes Succeed!\n%s" % sub_meta)
406 | except MNSExceptionBase as e:
407 | sys.stderr.write("Get Subscription Attributes Fail!\nException:%s\n\n" % e)
408 | sys.exit(1)
409 |
410 | #列出所有订阅
411 | ## prefix 指定订阅名称的前缀
412 | ## ret_number 单次list操作返回的最大订阅个数
413 | ## marker list操作的开始位置;当一次list操作不能列出所有订阅时,返回的next_marker作为下次list的marker参数
414 | try:
415 | prefix = u""
416 | ret_number = 10
417 | marker = u""
418 | total_count = 0
419 | while(True):
420 | sub_url_list, next_marker = my_topic.list_subscription(prefix, ret_number, marker)
421 | total_count += len(sub_url_list)
422 | for sub_url in sub_url_list:
423 | sys.stdout.write("SubscriptionURL:%s\n" % sub_url)
424 | if (next_marker == ""):
425 | break
426 | marker = next_marker
427 | sys.stdout.write("List Subscription Succeed! Subscription Count:%s\n\n" % total_count)
428 | except MNSExceptionBase as e:
429 | sys.stderr.write("List Subscription Fail!\nException:%s\n\n" % e)
430 | sys.exit(1)
431 |
432 | #发送消息
433 | ## msg_body 发布消息的正文
434 | ## msg_tag 消息标签(用户消息过滤),不能超过16个字符
435 | ## 返回如下属性:
436 | ## MessageId 消息编号
437 | ## MessageBodyMd5 消息正文的MD5值
438 | msg_body = "I am test message."
439 | msg_tag = "important"
440 | message = TopicMessage(msg_body, msg_tag)
441 | try:
442 | re_msg = my_topic.publish_message(message)
443 | sys.stdout.write("Publish Raw Message Succeed.\nMessageBody:%s\nMessageTag:%s\nMessageId:%s\nMessageBodyMd5:%s\n\n" % (msg_body, msg_tag, re_msg.message_id, re_msg.message_body_md5))
444 | except MNSExceptionBase as e:
445 | sys.stderr.write("Publish Raw Message Fail!\nException:%s\n\n" % e)
446 | sys.exit(1)
447 |
448 | message = Base64TopicMessage(msg_body, msg_tag)
449 | try:
450 | re_msg = my_topic.publish_message(message)
451 | sys.stdout.write("Publish Base64 encoded Message Succeed.\nMessageBody:%s\nMessageTag:%s\nMessageId:%s\nMessageBodyMd5:%s\n\n" % (msg_body, msg_tag, re_msg.message_id, re_msg.message_body_md5))
452 | except MNSExceptionBase as e:
453 | sys.stderr.write("Publish Base64 encoded Message Fail!\nException:%s\n\n" % e)
454 | sys.exit(1)
455 |
456 | sys.stdout.write("PASS ALL!!\n\n")
457 |
--------------------------------------------------------------------------------
/mns/queue.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | # Copyright (C) 2015, Alibaba Cloud Computing
3 |
4 | #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:
5 |
6 | #The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | #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.
9 |
10 | import time
11 | from .mns_client import MNSClient
12 | from .mns_request import *
13 | from .mns_exception import *
14 |
15 | class Queue:
16 | def __init__(self, queue_name, mns_client, debug=False):
17 | self.queue_name = queue_name
18 | self.mns_client = mns_client
19 | self.set_encoding(True)
20 | self.debug = debug
21 |
22 | def set_debug(self, debug):
23 | self.debug = debug
24 |
25 | def set_encoding(self, encoding):
26 | """ 设置是否对消息体进行base64编码
27 |
28 | @type encoding: bool
29 | @param encoding: 是否对消息体进行base64编码
30 | """
31 | self.encoding = encoding
32 |
33 | def create(self, queue_meta, req_info=None):
34 | """ 创建队列
35 |
36 | @type queue_meta: QueueMeta object
37 | @param queue_meta: QueueMeta对象,设置队列的属性
38 |
39 | @type req_info: RequestInfo object
40 | @param req_info: 透传到MNS的请求信息
41 |
42 | @rtype: string
43 | @return 新创建队列的URL
44 |
45 | @note: Exception
46 | :: MNSClientParameterException 参数格式异常
47 | :: MNSClientNetworkException 网络异常
48 | :: MNSServerException mns处理异常
49 | """
50 | req = CreateQueueRequest(self.queue_name, queue_meta.visibility_timeout, queue_meta.maximum_message_size, queue_meta.message_retention_period, queue_meta.delay_seconds, queue_meta.polling_wait_seconds, queue_meta.logging_enabled)
51 | req.set_req_info(req_info)
52 | resp = CreateQueueResponse()
53 | self.mns_client.create_queue(req, resp)
54 | self.debuginfo(resp)
55 | return resp.queue_url
56 |
57 | def get_attributes(self, req_info=None):
58 | """ 获取队列属性
59 |
60 | @rtype: QueueMeta object
61 | @return 队列的属性
62 |
63 | @type req_info: RequestInfo object
64 | @param req_info: 透传到MNS的请求信息
65 |
66 | @note: Exception
67 | :: MNSClientNetworkException 网络异常
68 | :: MNSServerException mns处理异常
69 | """
70 | req = GetQueueAttributesRequest(self.queue_name)
71 | req.set_req_info(req_info)
72 | resp = GetQueueAttributesResponse()
73 | self.mns_client.get_queue_attributes(req, resp)
74 | queue_meta = QueueMeta()
75 | self.__resp2meta__(queue_meta, resp)
76 | self.debuginfo(resp)
77 | return queue_meta
78 |
79 | def set_attributes(self, queue_meta, req_info=None):
80 | """ 设置队列属性
81 |
82 | @type queue_meta: QueueMeta object
83 | @param queue_meta: 新设置的属性
84 |
85 | @type req_info: RequestInfo object
86 | @param req_info: 透传到MNS的请求信息
87 |
88 | @note: Exception
89 | :: MNSClientParameterException 参数格式异常
90 | :: MNSClientNetworkException 网络异常
91 | :: MNSServerException mns处理异常
92 | """
93 | req = SetQueueAttributesRequest(self.queue_name, queue_meta.visibility_timeout, queue_meta.maximum_message_size, queue_meta.message_retention_period, queue_meta.delay_seconds, queue_meta.polling_wait_seconds, queue_meta.logging_enabled)
94 | req.set_req_info(req_info)
95 | resp = SetQueueAttributesResponse()
96 | self.mns_client.set_queue_attributes(req, resp)
97 | self.debuginfo(resp)
98 |
99 | def delete(self, req_info=None):
100 | """ 删除队列
101 |
102 | @type req_info: RequestInfo object
103 | @param req_info: 透传到MNS的请求信息
104 |
105 | @note: Exception
106 | :: MNSClientNetworkException 网络异常
107 | :: MNSServerException mns处理异常
108 | """
109 | req = DeleteQueueRequest(self.queue_name)
110 | req.set_req_info(req_info)
111 | resp = DeleteQueueResponse()
112 | self.mns_client.delete_queue(req, resp)
113 | self.debuginfo(resp)
114 |
115 | def send_message(self, message, req_info=None):
116 | """ 发送消息
117 |
118 | @type message: Message object
119 | @param message: 发送的Message object
120 |
121 | @type req_info: RequestInfo object
122 | @param req_info: 透传到MNS的请求信息
123 |
124 | @rtype: Message object
125 | @return 消息发送成功的返回属性,包含MessageId和MessageBodyMD5
126 |
127 | @note: Exception
128 | :: MNSClientParameterException 参数格式异常
129 | :: MNSClientNetworkException 网络异常
130 | :: MNSServerException mns处理异常
131 | """
132 | req = SendMessageRequest(self.queue_name, message.message_body, message.delay_seconds, message.priority, self.encoding)
133 | req.set_req_info(req_info)
134 | resp = SendMessageResponse()
135 | self.mns_client.send_message(req, resp)
136 | self.debuginfo(resp)
137 | return self.__send_resp2msg__(resp)
138 |
139 | def batch_send_message(self, messages, req_info=None):
140 | """批量发送消息
141 |
142 | @type messages: list of Message object
143 | @param messages: 发送的Message object list
144 |
145 | @type req_info: RequestInfo object
146 | @param req_info: 透传到MNS的请求信息
147 |
148 | @rtype: list of Message object
149 | @return 多条消息发送成功的返回属性,包含MessageId和MessageBodyMD5
150 |
151 | @note: Exception
152 | :: MNSClientParameterException 参数格式异常
153 | :: MNSClientNetworkException 网络异常
154 | :: MNSServerException mns处理异常
155 | """
156 | req = BatchSendMessageRequest(self.queue_name, self.encoding)
157 | req.set_req_info(req_info)
158 | for msg in messages:
159 | req.add_message(msg.message_body, msg.delay_seconds, msg.priority)
160 | resp = BatchSendMessageResponse()
161 | self.mns_client.batch_send_message(req, resp)
162 | self.debuginfo(resp)
163 | return self.__batchsend_resp2msg__(resp)
164 |
165 | def peek_message(self, req_info=None):
166 | """ 查看消息 消息体为字节串
167 |
168 | @type req_info: RequestInfo object
169 | @param req_info: 透传到MNS的请求信息
170 |
171 | @rtype: Message object
172 | @return: Message object中包含消息的基本属性
173 |
174 | @note: Exception
175 | :: MNSClientParameterException 参数格式异常
176 | :: MNSClientNetworkException 网络异常
177 | :: MNSServerException mns处理异常
178 | """
179 | req = PeekMessageRequest(self.queue_name, self.encoding)
180 | req.set_req_info(req_info)
181 | resp = PeekMessageResponse()
182 | self.mns_client.peek_message(req, resp)
183 | self.debuginfo(resp)
184 | return self.__peek_resp2msg__(resp)
185 |
186 | def peek_message_with_str_body(self, req_info=None):
187 | """ 查看消息 消息体为字符串
188 |
189 | @type req_info: RequestInfo object
190 | @param req_info: 透传到MNS的请求信息
191 |
192 | @rtype: Message object
193 | @return: Message object中包含消息的基本属性
194 |
195 | @note: Exception
196 | :: MNSClientParameterException 参数格式异常
197 | :: MNSClientNetworkException 网络异常
198 | :: MNSServerException mns处理异常
199 | """
200 | message = self.peek_message(req_info)
201 | if self.encoding:
202 | message.message_body = message.message_body.decode("utf-8")
203 |
204 | return message
205 |
206 | def batch_peek_message(self, batch_size, req_info=None):
207 | """ 批量查看消息 消息体为字节串
208 |
209 | @type batch_size: int
210 | @param batch_size: 本次请求最多获取的消息条数
211 |
212 | @type req_info: RequestInfo object
213 | @param req_info: 透传到MNS的请求信息
214 |
215 | @rtype: list of Message object
216 | @return 多条消息的属性,包含消息的基本属性
217 |
218 | @note: Exception
219 | :: MNSClientParameterException 参数格式异常
220 | :: MNSClientNetworkException 网络异常
221 | :: MNSServerException mns处理异常
222 | """
223 | req = BatchPeekMessageRequest(self.queue_name, batch_size, self.encoding)
224 | req.set_req_info(req_info)
225 | resp = BatchPeekMessageResponse()
226 | self.mns_client.batch_peek_message(req, resp)
227 | self.debuginfo(resp)
228 | return self.__batchpeek_resp2msg__(resp)
229 |
230 | def batch_peek_message_with_str_body(self, batch_size, req_info=None):
231 | """ 批量查看消息 消息体为字符串
232 |
233 | @type batch_size: int
234 | @param batch_size: 本次请求最多获取的消息条数
235 |
236 | @type req_info: RequestInfo object
237 | @param req_info: 透传到MNS的请求信息
238 |
239 | @rtype: list of Message object
240 | @return 多条消息的属性,包含消息的基本属性
241 |
242 | @note: Exception
243 | :: MNSClientParameterException 参数格式异常
244 | :: MNSClientNetworkException 网络异常
245 | :: MNSServerException mns处理异常
246 | """
247 | msg_list = self.batch_peek_message(batch_size, req_info)
248 | if self.encoding:
249 | for message in msg_list:
250 | message.message_body = message.message_body.decode("utf-8")
251 |
252 | return msg_list
253 |
254 | def receive_message(self, wait_seconds = -1, req_info=None):
255 | """ 消费消息 消息体为字节串
256 |
257 | @type wait_seconds: int
258 | @param wait_seconds: 本次请求的长轮询时间,单位:秒
259 |
260 | @type req_info: RequestInfo object
261 | @param req_info: 透传到MNS的请求信息
262 |
263 | @rtype: Message object
264 | @return Message object中包含基本属性、下次可消费时间和临时句柄
265 |
266 | @note: Exception
267 | :: MNSClientParameterException 参数格式异常
268 | :: MNSClientNetworkException 网络异常
269 | :: MNSServerException mns处理异常
270 | """
271 | req = ReceiveMessageRequest(self.queue_name, self.encoding, wait_seconds)
272 | req.set_req_info(req_info)
273 | resp = ReceiveMessageResponse()
274 | self.mns_client.receive_message(req, resp)
275 | self.debuginfo(resp)
276 | return self.__recv_resp2msg__(resp)
277 |
278 | def receive_message_with_str_body(self, wait_seconds = -1, req_info=None):
279 | """ 消费消息 消息体为字符串
280 |
281 | @type wait_seconds: int
282 | @param wait_seconds: 本次请求的长轮询时间,单位:秒
283 |
284 | @type req_info: RequestInfo object
285 | @param req_info: 透传到MNS的请求信息
286 |
287 | @rtype: Message object
288 | @return Message object中包含基本属性、下次可消费时间和临时句柄
289 |
290 | @note: Exception
291 | :: MNSClientParameterException 参数格式异常
292 | :: MNSClientNetworkException 网络异常
293 | :: MNSServerException mns处理异常
294 | """
295 | message = self.receive_message(wait_seconds, req_info)
296 | if self.encoding:
297 | message.message_body = message.message_body.decode("utf-8")
298 |
299 | return message
300 |
301 | def batch_receive_message(self, batch_size, wait_seconds = -1, req_info=None):
302 | """ 批量消费消息 消息体为字节串
303 |
304 | @type batch_size: int
305 | @param batch_size: 本次请求最多获取的消息条数
306 |
307 | @type wait_seconds: int
308 | @param wait_seconds: 本次请求的长轮询时间,单位:秒
309 |
310 | @type req_info: RequestInfo object
311 | @param req_info: 透传到MNS的请求信息
312 |
313 | @rtype: list of Message object
314 | @return 多条消息的属性,包含消息的基本属性、下次可消费时间和临时句柄
315 |
316 | @note: Exception
317 | :: MNSClientParameterException 参数格式异常
318 | :: MNSClientNetworkException 网络异常
319 | :: MNSServerException mns处理异常
320 | """
321 | req = BatchReceiveMessageRequest(self.queue_name, batch_size, self.encoding, wait_seconds)
322 | req.set_req_info(req_info)
323 | resp = BatchReceiveMessageResponse()
324 | self.mns_client.batch_receive_message(req, resp)
325 | self.debuginfo(resp)
326 | return self.__batchrecv_resp2msg__(resp)
327 |
328 | def batch_receive_message_with_str_body(self, batch_size, wait_seconds = -1, req_info=None):
329 | """ 批量消费消息 消息体为字符串
330 |
331 | @type batch_size: int
332 | @param batch_size: 本次请求最多获取的消息条数
333 |
334 | @type wait_seconds: int
335 | @param wait_seconds: 本次请求的长轮询时间,单位:秒
336 |
337 | @type req_info: RequestInfo object
338 | @param req_info: 透传到MNS的请求信息
339 |
340 | @rtype: list of Message object
341 | @return 多条消息的属性,包含消息的基本属性、下次可消费时间和临时句柄
342 |
343 | @note: Exception
344 | :: MNSClientParameterException 参数格式异常
345 | :: MNSClientNetworkException 网络异常
346 | :: MNSServerException mns处理异常
347 | """
348 | msg_list = self.batch_receive_message(batch_size, wait_seconds, req_info)
349 | if self.encoding:
350 | for message in msg_list:
351 | message.message_body = message.message_body.decode("utf-8")
352 |
353 | return msg_list
354 |
355 | def delete_message(self, receipt_handle, req_info=None):
356 | """ 删除消息
357 |
358 | @type receipt_handle: string
359 | @param receipt_handle: 最近一次操作该消息返回的临时句柄
360 |
361 | @type req_info: RequestInfo object
362 | @param req_info: 透传到MNS的请求信息
363 |
364 | @note: Exception
365 | :: MNSClientParameterException 参数格式异常
366 | :: MNSClientNetworkException 网络异常
367 | :: MNSServerException mns处理异常
368 | """
369 | req = DeleteMessageRequest(self.queue_name, receipt_handle)
370 | req.set_req_info(req_info)
371 | resp = DeleteMessageResponse()
372 | self.mns_client.delete_message(req, resp)
373 | self.debuginfo(resp)
374 |
375 | def batch_delete_message(self, receipt_handle_list, req_info=None):
376 | """批量删除消息
377 |
378 | @type receipt_handle_list: list
379 | @param receipt_handle_list: batch_receive_message返回的多条消息的临时句柄
380 |
381 | @type req_info: RequestInfo object
382 | @param req_info: 透传到MNS的请求信息
383 |
384 | @note: Exception
385 | :: MNSClientParameterException 参数格式异常
386 | :: MNSClientNetworkException 网络异常
387 | :: MNSServerException mns处理异常
388 | """
389 | req = BatchDeleteMessageRequest(self.queue_name, receipt_handle_list)
390 | req.set_req_info(req_info)
391 | resp = BatchDeleteMessageResponse()
392 | self.mns_client.batch_delete_message(req, resp)
393 | self.debuginfo(resp)
394 |
395 | def change_message_visibility(self, reciept_handle, visibility_timeout, req_info=None):
396 | """ 修改消息下次可消费时间
397 |
398 | @type reciept_handle: string
399 | @param reciept_handle: 最近一次操作该消息返回的临时句柄
400 |
401 | @type visibility_timeout: int
402 | @param visibility_timeout: 消息下次可被消费时间为
403 | now+visibility_timeout, 单位:秒
404 |
405 | @type req_info: RequestInfo object
406 | @param req_info: 透传到MNS的请求信息
407 |
408 | @rtype: Message object
409 | @return: Message object包含临时句柄和下次可消费时间
410 |
411 | @note: Exception
412 | :: MNSClientParameterException 参数格式异常
413 | :: MNSClientNetworkException 网络异常
414 | :: MNSServerException mns处理异常
415 | """
416 | req = ChangeMessageVisibilityRequest(self.queue_name, reciept_handle, visibility_timeout)
417 | req.set_req_info(req_info)
418 | resp = ChangeMessageVisibilityResponse()
419 | self.mns_client.change_message_visibility(req, resp)
420 | self.debuginfo(resp)
421 | return self.__changevis_resp2msg__(resp)
422 |
423 | def debuginfo(self, resp):
424 | if self.debug:
425 | print("===================DEBUG INFO===================")
426 | print("RequestId: %s" % resp.header["x-mns-request-id"])
427 | print("================================================")
428 |
429 | def __resp2meta__(self, queue_meta, resp):
430 | queue_meta.visibility_timeout = resp.visibility_timeout
431 | queue_meta.maximum_message_size = resp.maximum_message_size
432 | queue_meta.message_retention_period = resp.message_retention_period
433 | queue_meta.delay_seconds = resp.delay_seconds
434 | queue_meta.polling_wait_seconds = resp.polling_wait_seconds
435 | queue_meta.logging_enabled = resp.logging_enabled
436 |
437 | queue_meta.active_messages = resp.active_messages
438 | queue_meta.inactive_messages = resp.inactive_messages
439 | queue_meta.delay_messages = resp.delay_messages
440 | queue_meta.create_time = resp.create_time
441 | queue_meta.last_modify_time = resp.last_modify_time
442 | queue_meta.queue_name = resp.queue_name
443 |
444 | def __send_resp2msg__(self, resp):
445 | msg = Message()
446 | msg.message_id = resp.message_id
447 | msg.message_body_md5 = resp.message_body_md5
448 | msg.receipt_handle = resp.receipt_handle
449 | return msg
450 |
451 | def __batchsend_resp2msg__(self, resp):
452 | msg_list = []
453 | for entry in resp.message_list:
454 | msg = Message()
455 | msg.message_id = entry.message_id
456 | msg.message_body_md5 = entry.message_body_md5
457 | msg_list.append(msg)
458 | return msg_list
459 |
460 | def __peek_resp2msg__(self, resp):
461 | msg = Message()
462 | msg.message_id = resp.message_id
463 | msg.message_body_md5 = resp.message_body_md5
464 | msg.dequeue_count = resp.dequeue_count
465 | msg.enqueue_time = resp.enqueue_time
466 | msg.first_dequeue_time = resp.first_dequeue_time
467 | msg.message_body = resp.message_body
468 | msg.priority = resp.priority
469 | return msg
470 |
471 | def __batchpeek_resp2msg__(self, resp):
472 | msg_list = []
473 | for entry in resp.message_list:
474 | msg = Message()
475 | msg.message_id = entry.message_id
476 | msg.message_body_md5 = entry.message_body_md5
477 | msg.dequeue_count = entry.dequeue_count
478 | msg.enqueue_time = entry.enqueue_time
479 | msg.first_dequeue_time = entry.first_dequeue_time
480 | msg.message_body = entry.message_body
481 | msg.priority = entry.priority
482 | msg_list.append(msg)
483 | return msg_list
484 |
485 | def __recv_resp2msg__(self, resp):
486 | msg = self.__peek_resp2msg__(resp)
487 | msg.receipt_handle = resp.receipt_handle
488 | msg.next_visible_time = resp.next_visible_time
489 | return msg
490 |
491 | def __batchrecv_resp2msg__(self, resp):
492 | msg_list = []
493 | for entry in resp.message_list:
494 | msg = Message()
495 | msg.message_id = entry.message_id
496 | msg.message_body_md5 = entry.message_body_md5
497 | msg.dequeue_count = entry.dequeue_count
498 | msg.enqueue_time = entry.enqueue_time
499 | msg.first_dequeue_time = entry.first_dequeue_time
500 | msg.message_body = entry.message_body
501 | msg.priority = entry.priority
502 | msg.next_visible_time = entry.next_visible_time
503 | msg.receipt_handle = entry.receipt_handle
504 | msg_list.append(msg)
505 | return msg_list
506 |
507 | def __changevis_resp2msg__(self, resp):
508 | msg = Message()
509 | msg.receipt_handle = resp.receipt_handle
510 | msg.next_visible_time = resp.next_visible_time
511 | return msg
512 |
513 | class QueueMeta:
514 | DEFAULT_VISIBILITY_TIMEOUT = 30
515 | DEFAULT_MAXIMUM_MESSAGE_SIZE = 2048
516 | DEFAULT_MESSAGE_RETENTION_PERIOD = 86400
517 | DEFAULT_DELAY_SECONDS = 0
518 | DEFAULT_POLLING_WAIT_SECONDS = 0
519 | def __init__(self, vis_timeout = None, max_msg_size = None, msg_ttl = None, delay_sec = None, polling_wait_sec = None, logging_enabled = None):
520 | """ 队列属性
521 | @note: 设置属性
522 | :: visibility_timeout: message被receive后,持续不可消费的时间, 单位:秒
523 | :: maximum_message_size: message body的最大长度, 单位:Byte
524 | :: message_retention_period: message最长存活时间,单位:秒
525 | :: delay_seconds: 新message可消费的默认延迟时间,单位:秒
526 | :: polling_wait_seconds: receive message时,长轮询时间,单位:秒
527 | :: logging_enabled: 是否开启logging功能,如果开启MNS将该队列的日志推送到Account的logging bucket中
528 |
529 | @note: 非设置属性
530 | :: active_messages: 可消费消息数,近似值
531 | :: inactive_messages: 正在被消费的消息数,近似值
532 | :: delay_messages: 延迟消息数,近似值
533 | :: create_time: queue创建时间,单位:秒
534 | :: last_modify_time: 修改queue属性的最近时间,单位:秒
535 | :: queue_name: 队列名称
536 | """
537 | self.visibility_timeout = QueueMeta.DEFAULT_VISIBILITY_TIMEOUT if vis_timeout is None else vis_timeout
538 | self.maximum_message_size = QueueMeta.DEFAULT_MAXIMUM_MESSAGE_SIZE if max_msg_size is None else max_msg_size
539 | self.message_retention_period = QueueMeta.DEFAULT_MESSAGE_RETENTION_PERIOD if msg_ttl is None else msg_ttl
540 | self.delay_seconds = QueueMeta.DEFAULT_DELAY_SECONDS if delay_sec is None else delay_sec
541 | self.polling_wait_seconds = QueueMeta.DEFAULT_POLLING_WAIT_SECONDS if polling_wait_sec is None else polling_wait_sec
542 | self.logging_enabled = logging_enabled
543 |
544 | self.active_messages = -1
545 | self.inactive_messages = -1
546 | self.delay_messages = -1
547 | self.create_time = -1
548 | self.last_modify_time = -1
549 | self.queue_name = ""
550 |
551 | def set_visibilitytimeout(self, visibility_timeout):
552 | self.visibility_timeout = visibility_timeout
553 |
554 | def set_maximum_message_size(self, maximum_message_size):
555 | self.maximum_message_size = maximum_message_size
556 |
557 | def set_message_retention_period(self, message_retention_period):
558 | self.message_retention_period = message_retention_period
559 |
560 | def set_delay_seconds(self, delay_seconds):
561 | self.delay_seconds = delay_seconds
562 |
563 | def set_polling_wait_seconds(self, polling_wait_seconds):
564 | self.polling_wait_seconds = polling_wait_seconds
565 |
566 | def set_logging_enabled(self, logging_enabled):
567 | self.logging_enabled = logging_enabled
568 |
569 | def __str__(self):
570 | meta_info = {"VisibilityTimeout" : self.visibility_timeout,
571 | "MaximumMessageSize" : self.maximum_message_size,
572 | "MessageRetentionPeriod" : self.message_retention_period,
573 | "DelaySeconds" : self.delay_seconds,
574 | "PollingWaitSeconds" : self.polling_wait_seconds,
575 | "ActiveMessages" : self.active_messages,
576 | "InactiveMessages" : self.inactive_messages,
577 | "DelayMessages" : self.delay_messages,
578 | "CreateTime" : time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(self.create_time)),
579 | "LastModifyTime" : time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(self.last_modify_time)),
580 | "QueueName" : self.queue_name,
581 | "LoggingEnabled" : self.logging_enabled}
582 | return "\n".join(["%s: %s"%(k.ljust(30),v) for k,v in meta_info.items()])
583 |
584 | class Message:
585 | def __init__(self, message_body = None, delay_seconds = None, priority = None):
586 | """ 消息属性
587 |
588 | @note: send_message 指定属性
589 | :: message_body 消息体
590 | :: delay_seconds 消息延迟时间
591 | :: priority 消息优先级
592 |
593 | @note: send_message 返回属性
594 | :: message_id 消息编号
595 | :: message_body_md5 消息体的MD5值
596 |
597 | @note: peek_message 返回属性(基本属性)
598 | :: message_body 消息体
599 | :: message_id 消息编号
600 | :: message_body_md5 消息体的MD5值
601 | :: dequeue_count 消息被消费的次数
602 | :: enqueue_time 消息发送到队列的时间,单位:毫秒
603 | :: first_dequeue_time 消息第一次被消费的时间,单位:毫秒
604 |
605 | @note: receive_message 返回属性,除基本属性外
606 | :: receipt_handle 下次删除或修改消息的临时句柄,next_visible_time之前有效
607 | :: next_visible_time 消息下次可消费时间
608 |
609 | @note: change_message_visibility 返回属性
610 | :: receipt_handle
611 | :: next_visible_time
612 | """
613 | self.message_body = "" if message_body is None else message_body
614 | self.delay_seconds = -1 if delay_seconds is None else delay_seconds
615 | self.priority = -1 if priority is None else priority
616 |
617 | self.message_id = ""
618 | self.message_body_md5 = ""
619 |
620 | self.dequeue_count = -1
621 | self.enqueue_time = -1
622 | self.first_dequeue_time = -1
623 |
624 | self.receipt_handle = ""
625 | self.next_visible_time = 1
626 |
627 | def set_delayseconds(self, delay_seconds):
628 | self.delay_seconds = delay_seconds
629 |
630 | def set_priority(self, priority):
631 | self.priority = priority
632 |
633 |
--------------------------------------------------------------------------------