├── 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 | [![Github version](https://badgen.net/badge/color/1.2.0/green?label=version)](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 | --------------------------------------------------------------------------------