├── .gitignore ├── LICENSE ├── README.md ├── README.rst ├── mq_http_sdk ├── __init__.py ├── mq_client.py ├── mq_consumer.py ├── mq_exception.py ├── mq_http.py ├── mq_producer.py ├── mq_request.py ├── mq_tool.py ├── mq_xml_handler.py └── pkg_info.py ├── setup.conf └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | .idea 107 | .DS_STORE 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 aliyun.mq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MQ Python HTTP SDK 2 | Aliyun MQ Documents: http://www.aliyun.com/product/ons 3 | 4 | Aliyun MQ Console: https://ons.console.aliyun.com 5 | 6 | ## Requires 7 | 8 | Python version(=v1.0.0): [2.5, 3.0) 9 | 10 | Python version(>v1.0.0): [2.5, ~) 11 | 12 | ## Install sdk by pip 13 | 14 | 1. install pip, see [document](https://pip.pypa.io/en/stable/installing/) 15 | 2. install sdk by pip 16 | 17 | ```bash 18 | pip install mq_http_sdk 19 | ``` 20 | ## Notice 21 | 22 | MQClient, Producer, Consumer are not thread safe, please use multi instance in mutli thread. 23 | 24 | ## Note 25 | 1. Http consumer only support timer msg (less than 3 days), no matter the msg is produced from http or tcp protocol. 26 | 2. Order is only supported at special server cluster. 27 | 28 | ## Samples (github) 29 | 30 | [Publish Message](https://github.com/aliyunmq/mq-http-samples/blob/master/python/producer.py) 31 | 32 | [Consume Message](https://github.com/aliyunmq/mq-http-samples/blob/master/python/consumer.py) 33 | 34 | [Transaction Message](https://github.com/aliyunmq/mq-http-samples/blob/master/python/trans_producer.py) 35 | 36 | [Publish Order Message](https://github.com/aliyunmq/mq-http-samples/blob/master/python/order_producer.py) 37 | 38 | [Consume Order Message](https://github.com/aliyunmq/mq-http-samples/blob/master/python/order_consumer.py) 39 | 40 | ## Samples (code.aliyun.com) 41 | 42 | [Publish Message](https://code.aliyun.com/aliware_rocketmq/mq-http-samples/blob/master/python/producer.py) 43 | 44 | [Consume Message](https://code.aliyun.com/aliware_rocketmq/mq-http-samples/blob/master/python/consumer.py) 45 | 46 | [Transaction Message](https://code.aliyun.com/aliware_rocketmq/mq-http-samples/blob/master/python/trans_producer.py) 47 | 48 | [Publish Order Message](https://code.aliyun.com/aliware_rocketmq/mq-http-samples/blob/master/python/order_producer.py) 49 | 50 | [Consume Order Message](https://code.aliyun.com/aliware_rocketmq/mq-http-samples/blob/master/python/order_consumer.py) -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | MQ Python HTTP SDK 2 | ================== 3 | 4 | Aliyun MQ Documents: http://www.aliyun.com/product/ons 5 | 6 | Aliyun MQ Console: https://ons.console.aliyun.com 7 | 8 | Requires 9 | -------- 10 | 11 | Python version(=v1.0.0): [2.5, 3.0) 12 | 13 | Python version(>v1.0.0): [2.5, ~) 14 | 15 | Install sdk by pip 16 | ------------------ 17 | 18 | 1. install pip, see 19 | `document `__ 20 | 2. install sdk by pip 21 | 22 | .. code:: bash 23 | 24 | pip install mq_http_sdk 25 | 26 | Notice 27 | ------ 28 | 29 | MQClient, Producer, Consumer are not thread safe, please use multi 30 | instance in mutli thread. 31 | 32 | Note 33 | ---- 34 | 35 | 1. Http consumer only support timer msg (less than 3 days), no matter 36 | the msg is produced from http or tcp protocol. 37 | 2. Order is only supported at special server cluster. 38 | 39 | Samples (github) 40 | ---------------- 41 | 42 | `Publish 43 | Message `__ 44 | 45 | `Consume 46 | Message `__ 47 | 48 | `Transaction 49 | Message `__ 50 | 51 | `Publish Order 52 | Message `__ 53 | 54 | `Consume Order 55 | Message `__ 56 | 57 | Samples (code.aliyun.com) 58 | ------------------------- 59 | 60 | `Publish 61 | Message `__ 62 | 63 | `Consume 64 | Message `__ 65 | 66 | `Transaction 67 | Message `__ 68 | 69 | `Publish Order 70 | Message `__ 71 | 72 | `Consume Order 73 | Message `__ 74 | -------------------------------------------------------------------------------- /mq_http_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | name = "mq_http_sdk" 2 | -------------------------------------------------------------------------------- /mq_http_sdk/mq_client.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import base64 4 | import time 5 | import hashlib 6 | import hmac 7 | import platform 8 | from . import pkg_info 9 | from .mq_xml_handler import * 10 | from .mq_tool import * 11 | from .mq_http import * 12 | from .mq_consumer import MQConsumer 13 | from .mq_producer import MQProducer, MQTransProducer 14 | 15 | URI_SEC_MESSAGE = "messages" 16 | URI_SEC_TOPIC = "topics" 17 | MQ_VERSION_HEADER = "2015-06-06" 18 | 19 | 20 | class MQClient: 21 | def __init__(self, host, access_id, access_key, security_token="", debug=False, logger=None): 22 | """ 23 | @type host: string 24 | @param host: 访问的url,例如:http://$accountid.mqrest.cn-hangzhou.aliyuncs.com 25 | 26 | @type access_id: string 27 | @param access_id: 用户的AccessId, 阿里云官网获取 28 | 29 | @type access_key: string 30 | @param access_key: 用户的AccessKey,阿里云官网获取 31 | 32 | @type security_token: string 33 | @param security_token: 如果用户使用STS Token访问,需要提供security_token 34 | 35 | @note: Exception 36 | :: MQClientParameterException host格式错误 37 | """ 38 | self.host, self.is_https = self.process_host(host) 39 | self.access_id = access_id 40 | self.access_key = access_key 41 | self.version = MQ_VERSION_HEADER 42 | self.security_token = security_token 43 | self.logger = logger 44 | self.debug = debug 45 | self.http = MQHttp(self.host, logger=logger, is_https=self.is_https) 46 | if self.logger: 47 | self.logger.info("InitClient Host:%s Version:%s" % (host, self.version)) 48 | 49 | def get_consumer(self, instance_id, topic_name, consumer, message_tag=""): 50 | """ 获取MQClient的一个Consumer对象 51 | @type instance_id: string 52 | @param instance_id: 实例ID 53 | 54 | @type topic_name: string 55 | @param topic_name: topic名字 56 | 57 | @type consumer: string 58 | @param consumer: 消费者名字/mq cid 59 | 60 | @type message_tag: string 61 | @param message_tag: 消费过滤消息的tag,可空 62 | 63 | @rtype: MQConsumer object 64 | @return: 返回该MQClient的一个Consumer对象 65 | """ 66 | return MQConsumer(instance_id, topic_name, consumer, message_tag, self, self.debug) 67 | 68 | def get_producer(self, instance_id, topic_name): 69 | """ 获取MQClient的一个Producer对象 70 | @type instance_id: string 71 | @param instance_id: 实例ID 72 | 73 | @type topic_name: string 74 | @param topic_name: topic名字 75 | 76 | @rtype: MQProducer object 77 | @return: 返回该MQClient的一个Producer对象 78 | """ 79 | return MQProducer(instance_id, topic_name, self, self.debug) 80 | 81 | def get_trans_producer(self, instance_id, topic_name, group_id): 82 | """ 获取MQClient的一个事务发送者(MQTransProducer)对象 83 | @type instance_id: string 84 | @param instance_id: 实例ID 85 | 86 | @type topic_name: string 87 | @param topic_name: topic名字 88 | 89 | @type group_id: string 90 | @param group_id: 控制台申请的group id 91 | 92 | @rtype: MQTransProducer object 93 | @return: 返回该MQClient的一个事务发送者(MQTransProducer)对象 94 | """ 95 | return MQTransProducer(instance_id, topic_name, group_id, self, self.debug) 96 | 97 | def set_log_level(self, log_level): 98 | if self.logger: 99 | MQLogger.validate_loglevel(log_level) 100 | self.logger.setLevel(log_level) 101 | self.http.set_log_level(log_level) 102 | 103 | def close_log(self): 104 | self.logger = None 105 | self.http.close_log() 106 | 107 | def set_connection_timeout(self, connection_timeout): 108 | self.http.set_connection_timeout(connection_timeout) 109 | 110 | def set_keep_alive(self, keep_alive): 111 | self.http.set_keep_alive(keep_alive) 112 | 113 | def close_connection(self): 114 | self.http.conn.close() 115 | 116 | def consume_message(self, req, resp): 117 | # check parameter 118 | ConsumeMessageValidator.validate(req) 119 | 120 | # make request internal 121 | req_url = "/%s/%s/%s?consumer=%s&numOfMessages=%s" % (URI_SEC_TOPIC, req.topic_name, URI_SEC_MESSAGE, req.consumer, req.batch_size) 122 | if req.instance_id != "": 123 | req_url += "&ns=%s" % req.instance_id 124 | if req.wait_seconds != -1: 125 | req_url += "&waitseconds=%s" % req.wait_seconds 126 | if req.message_tag != "": 127 | req_url += "&tag=%s" % req.message_tag 128 | if req.trans != "": 129 | req_url += "&trans=%s" % req.trans 130 | 131 | req_inter = RequestInternal(req.method, req_url) 132 | self.build_header(req, req_inter) 133 | 134 | # send request 135 | resp_inter = self.http.send_request(req_inter) 136 | 137 | # handle result, make response 138 | resp.status = resp_inter.status 139 | resp.header = resp_inter.header 140 | self.check_status(resp_inter, resp) 141 | if resp.error_data == "": 142 | resp.message_list = ConsumeMessageDecoder.decode(resp_inter.data, resp.get_req_id()) 143 | if self.logger: 144 | self.logger.info("ConsumeMessage RequestId:%s TopicName:%s WaitSeconds:%s BatchSize:%s Tag:%s MessageCount:%s \ 145 | MessagesInfo\n%s" % ( 146 | resp.get_req_id(), req.topic_name, req.wait_seconds, req.batch_size, req.message_tag, len(resp.message_list), \ 147 | "\n".join([ 148 | "MessageId:%s MessageBodyMD5:%s NextConsumeTime:%s ReceiptHandle:%s PublishTime:%s ConsumedTimes:%s" % \ 149 | (msg.message_id, msg.message_body_md5, msg.next_consume_time, msg.receipt_handle, 150 | msg.publish_time, msg.consumed_times) for msg in resp.message_list]))) 151 | 152 | def ack_message(self, req, resp): 153 | # check parameter 154 | AckMessageValidator.validate(req) 155 | 156 | # make request internal 157 | req_url = "/%s/%s/%s?consumer=%s" % (URI_SEC_TOPIC, req.topic_name, URI_SEC_MESSAGE, req.consumer) 158 | if req.instance_id != "": 159 | req_url += "&ns=%s" % req.instance_id 160 | if req.trans != "": 161 | req_url += "&trans=%s" % req.trans 162 | 163 | req_inter = RequestInternal(req.method, req_url) 164 | req_inter.data = ReceiptHandlesEncoder.encode(req.receipt_handle_list) 165 | self.build_header(req, req_inter) 166 | 167 | # send request 168 | resp_inter = self.http.send_request(req_inter) 169 | 170 | # handle result, make response 171 | resp.status = resp_inter.status 172 | resp.header = resp_inter.header 173 | self.check_status(resp_inter, resp, AckMessageDecoder) 174 | if self.logger: 175 | self.logger.info("AckMessage RequestId:%s TopicName:%s ReceiptHandles\n%s" % \ 176 | (resp.get_req_id(), req.topic_name, "\n".join(req.receipt_handle_list))) 177 | 178 | def publish_message(self, req, resp): 179 | # check parameter 180 | PublishMessageValidator.validate(req) 181 | 182 | # make request internal 183 | req_url = "/%s/%s/%s" % (URI_SEC_TOPIC, req.topic_name, URI_SEC_MESSAGE) 184 | if req.instance_id != "": 185 | req_url += "?ns=%s" % req.instance_id 186 | 187 | req_inter = RequestInternal(req.method, req_url) 188 | req_inter.data = TopicMessageEncoder.encode(req) 189 | self.build_header(req, req_inter) 190 | 191 | # send request 192 | resp_inter = self.http.send_request(req_inter) 193 | 194 | # handle result, make response 195 | resp.status = resp_inter.status 196 | resp.header = resp_inter.header 197 | self.check_status(resp_inter, resp) 198 | if resp.error_data == "": 199 | resp.message_id, resp.message_body_md5, resp.receipt_handle = PublishMessageDecoder.decode(resp_inter.data, 200 | resp.get_req_id()) 201 | if self.logger: 202 | self.logger.info("PublishMessage RequestId:%s TopicName:%s MessageId:%s MessageBodyMD5:%s" % \ 203 | (resp.get_req_id(), req.topic_name, resp.message_id, resp.message_body_md5)) 204 | 205 | ################################################################################################### 206 | # ----------------------internal-------------------------------------------------------------------# 207 | def build_header(self, req, req_inter): 208 | if self.http.is_keep_alive(): 209 | req_inter.header["Connection"] = "Keep-Alive" 210 | if req_inter.data != "": 211 | req_inter.header["content-type"] = "text/xml;charset=UTF-8" 212 | req_inter.header["x-mq-version"] = self.version 213 | req_inter.header["host"] = self.host 214 | req_inter.header["date"] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) 215 | req_inter.header["user-agent"] = "mq-python-sdk/%s(%s/%s;%s)" % \ 216 | (pkg_info.version, platform.system(), platform.release(), 217 | platform.python_version()) 218 | req_inter.header["Authorization"] = self.get_signature(req_inter.method, req_inter.header, req_inter.uri) 219 | if self.security_token != "": 220 | req_inter.header["security-token"] = self.security_token 221 | 222 | def get_signature(self, method, headers, resource): 223 | content_md5 = self.get_element('content-md5', headers) 224 | content_type = self.get_element('content-type', headers) 225 | date = self.get_element('date', headers) 226 | canonicalized_resource = resource 227 | canonicalized_mq_headers = "" 228 | if len(headers) > 0: 229 | x_header_list = list(headers.keys()) 230 | x_header_list.sort() 231 | for k in x_header_list: 232 | if k.startswith('x-mq-'): 233 | canonicalized_mq_headers += k + ":" + headers[k] + "\n" 234 | string_to_sign = "%s\n%s\n%s\n%s\n%s%s" % ( 235 | method, content_md5, content_type, date, canonicalized_mq_headers, canonicalized_resource) 236 | # hmac only support str in python2.7 237 | 238 | if sys.version > '3': 239 | tmp_key = self.access_key.encode('utf-8') if isinstance(self.access_key, str) else self.access_key 240 | h = hmac.new(tmp_key, string_to_sign.encode('utf-8'), hashlib.sha1) 241 | signature = base64.b64encode(h.digest()) 242 | signature = "MQ " + self.access_id + ":" + signature.decode('utf-8') 243 | return signature 244 | else: 245 | tmp_key = self.access_key.encode('utf-8') if isinstance(self.access_key, unicode) else self.access_key 246 | h = hmac.new(tmp_key, string_to_sign, hashlib.sha1) 247 | signature = base64.b64encode(h.digest()) 248 | signature = "MQ " + self.access_id + ":" + signature 249 | return signature 250 | 251 | def get_element(self, name, container): 252 | if name in container: 253 | return container[name] 254 | else: 255 | return "" 256 | 257 | def check_status(self, resp_inter, resp, decoder=ErrorDecoder): 258 | if 200 <= resp_inter.status < 400: 259 | resp.error_data = "" 260 | else: 261 | resp.error_data = resp_inter.data 262 | if 400 <= resp_inter.status <= 600: 263 | excType, excMessage, reqId, hostId, subErr = decoder.decodeError(resp.error_data, 264 | resp.get_req_id()) 265 | if reqId is None: 266 | reqId = resp.header["x-mq-request-id"] 267 | raise MQServerException(excType, excMessage, reqId, hostId, subErr) 268 | else: 269 | raise MQClientNetworkException("UnkownError", resp_inter.data, resp.get_req_id()) 270 | 271 | def process_host(self, host): 272 | if host.startswith("http://"): 273 | if host.endswith("/"): 274 | host = host[:-1] 275 | host = host[len("http://"):] 276 | return host, False 277 | elif host.startswith("https://"): 278 | if host.endswith("/"): 279 | host = host[:-1] 280 | host = host[len("https://"):] 281 | return host, True 282 | else: 283 | raise MQClientParameterException("InvalidHost", "Only support http prototol. Invalid host:%s" % host) 284 | -------------------------------------------------------------------------------- /mq_http_sdk/mq_consumer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import sys 4 | from .mq_request import * 5 | from .mq_tool import * 6 | if sys.version > '3': 7 | from urllib.parse import quote 8 | else: 9 | from urllib import quote 10 | 11 | 12 | class MQConsumer: 13 | def __init__(self, instance_id, topic_name, consumer, message_tag, mq_client, debug=False): 14 | if instance_id is None: 15 | self.instance_id = "" 16 | else: 17 | self.instance_id = instance_id 18 | self.topic_name = topic_name 19 | self.consumer = consumer 20 | if message_tag is None: 21 | self.message_tag = "" 22 | else: 23 | self.message_tag = quote(message_tag) 24 | self.mq_client = mq_client 25 | self.debug = debug 26 | 27 | def set_debug(self, debug): 28 | self.debug = debug 29 | 30 | def consume_message(self, batch_size=1, wait_seconds=-1): 31 | """ 消费消息 32 | 33 | @type batch_size: int 34 | @param batch_size: 本次请求最多获取的消息条数,1~16 35 | 36 | @type wait_seconds: int 37 | @param wait_seconds: 本次请求的长轮询时间,单位:秒,1~30 38 | 39 | @rtype: list of Message object 40 | @return 多条消息的属性,包含消息的基本属性、下次可消费时间和临时句柄 41 | 42 | @note: Exception 43 | :: MQClientParameterException 参数格式异常 44 | :: MQClientNetworkException 网络异常 45 | :: MQServerException 处理异常 46 | """ 47 | req = ConsumeMessageRequest(self.instance_id, self.topic_name, self.consumer, batch_size, self.message_tag, wait_seconds) 48 | resp = ConsumeMessageResponse() 49 | self.mq_client.consume_message(req, resp) 50 | self.debuginfo(resp) 51 | return self.__batchrecv_resp2msg__(resp) 52 | 53 | def consume_message_orderly(self, batch_size=1, wait_seconds=-1): 54 | """ 顺序消费消息,拿到的消息可能是多个分区的(对于分区顺序)一个分区的内的消息一定是顺序的 55 | 对于顺序消费,如果一个分区内的消息只要有没有被确认消费 {ack_message} 成功,则对于这个分区在NextConsumeTime后还会消费到相同的消息 56 | 对于一个分区,只有所有消息确认消费成功才能消费下一批消息 57 | 58 | @type batch_size: int 59 | @param batch_size: 本次请求最多获取的消息条数,1~16 60 | 61 | @type wait_seconds: int 62 | @param wait_seconds: 本次请求的长轮询时间,单位:秒,1~30 63 | 64 | @rtype: list of Message object 65 | @return 多条消息的属性,包含消息的基本属性、下次可消费时间和临时句柄 66 | 67 | @note: Exception 68 | :: MQClientParameterException 参数格式异常 69 | :: MQClientNetworkException 网络异常 70 | :: MQServerException 处理异常 71 | """ 72 | req = ConsumeMessageRequest(self.instance_id, self.topic_name, self.consumer, batch_size, self.message_tag, wait_seconds) 73 | req.set_order() 74 | resp = ConsumeMessageResponse() 75 | self.mq_client.consume_message(req, resp) 76 | self.debuginfo(resp) 77 | return self.__batchrecv_resp2msg__(resp) 78 | 79 | def ack_message(self, receipt_handle_list): 80 | """确认消息消费成功,如果未在300秒内确认则认为消息消费失败,通过consume_message能再次收到改消息 81 | 82 | @type receipt_handle_list: list, size: 1~16 83 | @param receipt_handle_list: consume_message返回的多条消息的临时句柄 84 | 85 | @note: Exception 86 | :: MQClientParameterException 参数格式异常 87 | :: MQClientNetworkException 网络异常 88 | :: MQServerException 处理异常 89 | """ 90 | req = AckMessageRequest(self.instance_id, self.topic_name, self.consumer, receipt_handle_list) 91 | resp = AckMessageResponse() 92 | self.mq_client.ack_message(req, resp) 93 | self.debuginfo(resp) 94 | 95 | def debuginfo(self, resp): 96 | if self.debug: 97 | print("===================DEBUG INFO===================") 98 | print("RequestId: %s" % resp.header["x-mq-request-id"]) 99 | print("================================================") 100 | 101 | def __batchrecv_resp2msg__(self, resp): 102 | msg_list = [] 103 | for entry in resp.message_list: 104 | msg = Message() 105 | msg.message_id = entry.message_id 106 | msg.message_body_md5 = entry.message_body_md5 107 | msg.consumed_times = entry.consumed_times 108 | msg.publish_time = entry.publish_time 109 | msg.first_consume_time = entry.first_consume_time 110 | msg.message_body = entry.message_body 111 | msg.next_consume_time = entry.next_consume_time 112 | msg.receipt_handle = entry.receipt_handle 113 | msg.message_tag = entry.message_tag 114 | msg.properties = MQUtils.string_to_map(entry.properties) 115 | msg_list.append(msg) 116 | return msg_list 117 | 118 | 119 | class Message: 120 | def __init__(self): 121 | """ 消息 122 | :: message_body 消息体 123 | :: message_id 消息编号 124 | :: message_body_md5 消息体的MD5值 125 | :: consumed_times 消息被消费的次数 126 | :: publish_time 消息发送的时间,单位:毫秒 127 | :: first_consume_time 消息第一次被消费的时间,单位:毫秒 128 | :: receipt_handle 下次删除的临时句柄,next_consume_time之前有效 129 | :: next_consume_time 消息下次可消费时间 130 | :: properties 消息的属性 131 | """ 132 | self.message_body = "" 133 | self.message_tag = None 134 | 135 | self.message_id = "" 136 | self.message_body_md5 = "" 137 | 138 | self.consumed_times = -1 139 | self.publish_time = -1 140 | self.first_consume_time = -1 141 | 142 | self.receipt_handle = "" 143 | self.next_consume_time = 1 144 | 145 | self.properties = {} 146 | 147 | def get_message_key(self): 148 | return self.get_property("KEYS") 149 | 150 | def get_start_deliver_time(self): 151 | v = self.get_property("__STARTDELIVERTIME") 152 | if v == "": 153 | return 0 154 | 155 | return int(v) 156 | 157 | def get_trans_check_immunity_time(self): 158 | v = self.get_property("__TransCheckT") 159 | if v == "": 160 | return 0 161 | 162 | return int(v) 163 | 164 | def get_sharding_key(self): 165 | return self.get_property("__SHARDINGKEY") 166 | 167 | def get_property(self, key): 168 | if key in self.properties: 169 | return self.properties[key] 170 | 171 | return "" 172 | -------------------------------------------------------------------------------- /mq_http_sdk/mq_exception.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | class MQExceptionBase(Exception): 5 | """ 6 | @type type: string 7 | @param type: 错误类型 8 | 9 | @type message: string 10 | @param message: 错误描述 11 | 12 | @type req_id: string 13 | @param req_id: 请求的request_id 14 | """ 15 | 16 | def __init__(self, type, message, req_id=None): 17 | self.type = type 18 | self.message = message 19 | self.req_id = req_id 20 | 21 | def get_info(self): 22 | if self.req_id is not None: 23 | return "(\"%s\" \"%s\") RequestID:%s\n" % (self.type, self.message, self.req_id) 24 | else: 25 | return "(\"%s\" \"%s\")\n" % (self.type, self.message) 26 | 27 | def __str__(self): 28 | return "MQExceptionBase %s" % (self.get_info()) 29 | 30 | 31 | class MQClientException(MQExceptionBase): 32 | def __init__(self, type, message, req_id=None): 33 | MQExceptionBase.__init__(self, type, message, req_id) 34 | 35 | def __str__(self): 36 | return "MQClientException %s" % (self.get_info()) 37 | 38 | 39 | class MQServerException(MQExceptionBase): 40 | """ 处理异常 41 | 42 | @note: 根据type进行分类处理,常见错误类型: 43 | : InvalidArgument 参数不合法 44 | : AccessDenied 无权对该资源进行当前操作 45 | : TopicNotExist 主题不存在 46 | : MessageNotExist 队列中没有消息 47 | : 更多错误类型请移步阿里云消息和通知服务官网进行了解; 48 | """ 49 | 50 | def __init__(self, type, message, request_id, host_id, sub_errors=None): 51 | MQExceptionBase.__init__(self, type, message, request_id) 52 | self.request_id = request_id 53 | self.host_id = host_id 54 | self.sub_errors = sub_errors 55 | 56 | def __str__(self): 57 | return "MQServerException %s" % (self.get_info()) 58 | 59 | 60 | class MQClientNetworkException(MQClientException): 61 | """ 网络异常 62 | 63 | @note: 检查endpoint是否正确、本机网络是否正常等; 64 | """ 65 | 66 | def __init__(self, type, message, req_id=None): 67 | MQClientException.__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 "MQClientNetworkException %s" % (self.get_info()) 74 | 75 | 76 | class MQClientParameterException(MQClientException): 77 | """ 参数格式错误 78 | 79 | @note: 请根据提示修改对应参数; 80 | """ 81 | 82 | def __init__(self, type, message, req_id=None): 83 | MQClientException.__init__(self, type, message, req_id) 84 | 85 | def __str__(self): 86 | return "MQClientParameterException %s" % (self.get_info()) 87 | -------------------------------------------------------------------------------- /mq_http_sdk/mq_http.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import socket 4 | import sys 5 | from .mq_exception import * 6 | if sys.version > '3': 7 | from http.client import HTTPConnection, BadStatusLine, HTTPSConnection 8 | else: 9 | from httplib import HTTPConnection, BadStatusLine, HTTPSConnection 10 | 11 | 12 | class MQHTTPConnection(HTTPConnection): 13 | def __init__(self, host, port=None, strict=None, connection_timeout=60): 14 | HTTPConnection.__init__(self, host, port, strict) 15 | self.request_length = 0 16 | self.connection_timeout = connection_timeout 17 | 18 | def send(self, str): 19 | HTTPConnection.send(self, str) 20 | self.request_length += len(str) 21 | 22 | def request(self, method, url, body=None, headers={}): 23 | self.request_length = 0 24 | HTTPConnection.request(self, method, url, body, headers) 25 | 26 | def connect(self): 27 | msg = "getaddrinfo returns an empty list" 28 | for res in socket.getaddrinfo(self.host, self.port, 0, 29 | socket.SOCK_STREAM): 30 | af, socktype, proto, canonname, sa = res 31 | try: 32 | self.sock = socket.socket(af, socktype, proto) 33 | self.sock.settimeout(self.connection_timeout) 34 | if self.debuglevel > 0: 35 | print("connect: (%s, %s)" % (self.host, self.port)) 36 | self.sock.connect(sa) 37 | except socket.error as e: 38 | msg = e 39 | if self.debuglevel > 0: 40 | print('connect fail:', (self.host, self.port)) 41 | if self.sock: 42 | self.sock.close() 43 | self.sock = None 44 | continue 45 | break 46 | if not self.sock: 47 | raise socket.error(msg) 48 | 49 | class MQHTTPSConnection(HTTPSConnection): 50 | def __init__(self, host, port=None, strict=None): 51 | HTTPSConnection.__init__(self, host, port, strict=strict) 52 | self.request_length = 0 53 | 54 | def send(self, str): 55 | HTTPSConnection.send(self, str) 56 | self.request_length += len(str) 57 | 58 | def request(self, method, url, body=None, headers={}): 59 | self.request_length = 0 60 | HTTPSConnection.request(self, method, url, body, headers) 61 | 62 | 63 | class MQHttp: 64 | def __init__(self, host, connection_timeout=60, keep_alive=True, logger=None, is_https=False): 65 | if is_https: 66 | self.conn = MQHTTPSConnection(host) 67 | else: 68 | self.conn = MQHTTPConnection(host, connection_timeout=connection_timeout) 69 | self.host = host 70 | self.is_https = is_https 71 | self.connection_timeout = connection_timeout 72 | self.keep_alive = keep_alive 73 | self.request_size = 0 74 | self.response_size = 0 75 | self.logger = logger 76 | if self.logger: 77 | self.logger.info("InitOnsHttp KeepAlive:%s ConnectionTime:%s" % (self.keep_alive, self.connection_timeout)) 78 | 79 | def set_log_level(self, log_level): 80 | if self.logger: 81 | self.logger.setLevel(log_level) 82 | 83 | def close_log(self): 84 | self.logger = None 85 | 86 | def set_connection_timeout(self, connection_timeout): 87 | self.connection_timeout = connection_timeout 88 | if not self.is_https: 89 | if self.conn: 90 | self.conn.close() 91 | self.conn = MQHTTPConnection(self.host, connection_timeout=connection_timeout) 92 | 93 | def set_keep_alive(self, keep_alive): 94 | self.keep_alive = keep_alive 95 | 96 | def is_keep_alive(self): 97 | return self.keep_alive 98 | 99 | def send_request(self, req_inter): 100 | try: 101 | if self.logger: 102 | self.logger.debug("SendRequest %s" % req_inter) 103 | self.conn.request(req_inter.method, req_inter.uri, req_inter.data, req_inter.header) 104 | self.conn.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 105 | try: 106 | http_resp = self.conn.getresponse() 107 | except BadStatusLine: 108 | # open another connection when keep-alive timeout 109 | # httplib will not handle keep-alive timeout, so we must handle it ourself 110 | self.conn.close() 111 | self.conn.request(req_inter.method, req_inter.uri, req_inter.data, req_inter.header) 112 | self.conn.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 113 | http_resp = self.conn.getresponse() 114 | headers = dict(http_resp.getheaders()) 115 | resp_inter = ResponseInternal(status=http_resp.status, header=headers, data=http_resp.read()) 116 | self.request_size = self.conn.request_length 117 | self.response_size = len(resp_inter.data) 118 | if not self.is_keep_alive(): 119 | self.conn.close() 120 | if self.logger: 121 | self.logger.debug("GetResponse %s" % resp_inter) 122 | return resp_inter 123 | except Exception as e: 124 | self.conn.close() 125 | raise MQClientNetworkException("NetWorkException", str(e)) # raise netException 126 | 127 | 128 | class RequestInternal: 129 | def __init__(self, method="", uri="", header=None, data=""): 130 | if header == None: 131 | header = {} 132 | self.method = method 133 | self.uri = uri 134 | self.header = header 135 | self.data = data 136 | 137 | def __str__(self): 138 | return "Method: %s\nUri: %s\nHeader: %s\nData: %s\n" % \ 139 | (self.method, self.uri, "\n".join(["%s: %s" % (k, v) for k, v in list(self.header.items())]), self.data) 140 | 141 | 142 | class ResponseInternal: 143 | def __init__(self, status=0, header=None, data=""): 144 | if header == None: 145 | header = {} 146 | self.status = status 147 | self.header = header 148 | self.data = data 149 | 150 | def __str__(self): 151 | return "Status: %s\nHeader: %s\nData: %s\n" % \ 152 | (self.status, "\n".join(["%s: %s" % (k, v) for k, v in list(self.header.items())]), self.data) 153 | -------------------------------------------------------------------------------- /mq_http_sdk/mq_producer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from .mq_request import * 4 | from .mq_tool import * 5 | from .mq_exception import * 6 | from .mq_consumer import Message 7 | 8 | try: 9 | import json 10 | except ImportError as e: 11 | import simplejson as json 12 | 13 | 14 | class MQProducer: 15 | def __init__(self, instance_id, topic_name, mq_client, debug=False): 16 | if instance_id is None: 17 | self.instance_id = "" 18 | else: 19 | self.instance_id = instance_id 20 | self.topic_name = topic_name 21 | self.mq_client = mq_client 22 | self.debug = debug 23 | 24 | def set_debug(self, debug): 25 | self.debug = debug 26 | 27 | def publish_message(self, message): 28 | """ 发送消息 29 | 30 | @type message: TopicMessage object 31 | @param message: 发布的TopicMessage object 32 | 33 | @rtype: TopicMessage object 34 | @return: 消息发布成功的返回属性,包含MessageId和MessageBodyMD5 35 | 36 | @note: Exception 37 | :: MQClientParameterException 参数格式异常 38 | :: MQClientNetworkException 网络异常 39 | :: MQServerException 处理异常 40 | """ 41 | msg_properties_str = MQUtils.map_to_string(message.properties) 42 | req = PublishMessageRequest(self.instance_id, self.topic_name, message.message_body, message.message_tag, 43 | msg_properties_str) 44 | resp = PublishMessageResponse() 45 | self.mq_client.publish_message(req, resp) 46 | self.debuginfo(resp) 47 | return self.__publish_resp2msg__(resp) 48 | 49 | def debuginfo(self, resp): 50 | if self.debug: 51 | print("===================DEBUG INFO===================") 52 | print("RequestId: %s" % resp.header["x-mq-request-id"]) 53 | print("================================================") 54 | 55 | def __publish_resp2msg__(self, resp): 56 | msg = TopicMessage() 57 | msg.message_id = resp.message_id 58 | msg.message_body_md5 = resp.message_body_md5 59 | msg.receipt_handle = resp.receipt_handle 60 | return msg 61 | 62 | 63 | class TopicMessage: 64 | def __init__(self, message_body="", message_tag=""): 65 | """ Specify information of TopicMessage 66 | 67 | @note: publish_message params 68 | :: message_body string 69 | :: message_tag string, used to filter message 70 | 71 | @note: publish_message response information 72 | :: message_id 73 | :: message_body_md5 74 | :: receipt_handle string, only publish transaction msg contains, is valid 75 | before TransCheckImmunityTime 76 | """ 77 | self.message_body = message_body 78 | self.message_tag = message_tag 79 | self.properties = {} 80 | 81 | self.message_id = "" 82 | self.message_body_md5 = "" 83 | self.receipt_handle = "" 84 | 85 | def set_message_body(self, message_body): 86 | self.message_body = message_body 87 | 88 | def set_message_tag(self, message_tag): 89 | self.message_tag = message_tag 90 | 91 | def set_message_key(self, key): 92 | """ Set Message Key 93 | @type key: str 94 | @param key: message key 95 | """ 96 | self.properties["KEYS"] = str(key) 97 | 98 | def set_trans_check_immunity_time(self, time_in_seconds): 99 | """ 在消息属性中添加第一次消息回查的最快时间,单位秒,并且表征这是一条事务消息 100 | @type time_in_seconds: int 101 | @param time_in_seconds: 第一次消息事务回查的时间,单位:秒 102 | """ 103 | self.properties["__TransCheckT"] = str(time_in_seconds) 104 | 105 | def set_start_deliver_time(self, time_in_millis): 106 | """ 定时消息,单位毫秒(ms),在指定时间戳(当前时间之后)进行投递。 107 | @type time_in_millis: long 108 | @param time_in_millis: 定时时间戳,单位:秒 109 | """ 110 | self.properties["__STARTDELIVERTIME"] = str(time_in_millis) 111 | 112 | def set_sharding_key(self, sharding_key): 113 | """ 分区顺序消息中区分不同分区的关键字段,sharding key 于普通消息的 key 是完全不同的概念。 114 | 全局顺序消息,该字段可以设置为任意非空字符串。 115 | @type sharding_key: str 116 | @param sharding_key: 分区消息键值 117 | """ 118 | self.properties["__SHARDINGKEY"] = sharding_key 119 | 120 | def put_property(self, key, value): 121 | """ 设置消息的属性 122 | @type key: str 123 | @param key: 属性键 124 | 125 | @type value: str 126 | @param value: 属性值 127 | """ 128 | self.properties[str(key)] = str(value) 129 | 130 | 131 | class MQTransProducer(MQProducer): 132 | def __init__(self, instance_id, topic_name, group_id, mq_client, debug=False): 133 | MQProducer.__init__(self, instance_id, topic_name, mq_client, debug) 134 | if group_id is None or group_id == "": 135 | raise MQClientParameterException("InitMQTransProducerError", "groupId is None or empty") 136 | self.group_id = group_id 137 | 138 | def consume_half_message(self, batch_size=1, wait_seconds=-1): 139 | """ 消费事务半消息 140 | 141 | @type batch_size: int 142 | @param batch_size: 本次请求最多获取的消息条数,1~16 143 | 144 | @type wait_seconds: int 145 | @param wait_seconds: 本次请求的长轮询时间,单位:秒,1~30 146 | 147 | @rtype: list of Message object 148 | @return 多条事务半消息消息,包含消息的基本属性、下次可消费时间和临时句柄 149 | 150 | @note: Exception 151 | :: MQClientParameterException 参数格式异常 152 | :: MQClientNetworkException 网络异常 153 | :: MQServerException 处理异常 154 | """ 155 | req = ConsumeMessageRequest(self.instance_id, self.topic_name, self.group_id, batch_size, "", wait_seconds) 156 | req.set_trans_pop() 157 | resp = ConsumeMessageResponse() 158 | self.mq_client.consume_message(req, resp) 159 | self.debuginfo(resp) 160 | return self.__batchrecv_resp2msg__(resp) 161 | 162 | def commit(self, receipt_handle): 163 | """提交事务消息 164 | 165 | @type receipt_handle: basestring 166 | @param receipt_handle: consume_half_message返回的单条消息句柄或者是发送事务消息返回的句柄 167 | 168 | @note: Exception 169 | :: MQClientParameterException 参数格式异常 170 | :: MQClientNetworkException 网络异常 171 | :: MQServerException 处理异常 172 | """ 173 | req = AckMessageRequest(self.instance_id, self.topic_name, self.group_id, [receipt_handle]) 174 | req.set_trans_commit() 175 | resp = AckMessageResponse() 176 | self.mq_client.ack_message(req, resp) 177 | self.debuginfo(resp) 178 | 179 | def rollback(self, receipt_handle): 180 | """取消事务消息 181 | 182 | @type receipt_handle: basestring 183 | @param receipt_handle: consume_half_message返回的单条消息句柄或者是发送事务消息返回的句柄 184 | 185 | @note: Exception 186 | :: MQClientParameterException 参数格式异常 187 | :: MQClientNetworkException 网络异常 188 | :: MQServerException 处理异常 189 | """ 190 | req = AckMessageRequest(self.instance_id, self.topic_name, self.group_id, [receipt_handle]) 191 | req.set_trans_rollback() 192 | resp = AckMessageResponse() 193 | self.mq_client.ack_message(req, resp) 194 | self.debuginfo(resp) 195 | 196 | def __batchrecv_resp2msg__(self, resp): 197 | msg_list = [] 198 | for entry in resp.message_list: 199 | msg = Message() 200 | msg.message_id = entry.message_id 201 | msg.message_body_md5 = entry.message_body_md5 202 | msg.consumed_times = entry.consumed_times 203 | msg.publish_time = entry.publish_time 204 | msg.first_consume_time = entry.first_consume_time 205 | msg.message_body = entry.message_body 206 | msg.next_consume_time = entry.next_consume_time 207 | msg.receipt_handle = entry.receipt_handle 208 | msg.message_tag = entry.message_tag 209 | msg.properties = MQUtils.string_to_map(entry.properties) 210 | msg_list.append(msg) 211 | return msg_list 212 | 213 | -------------------------------------------------------------------------------- /mq_http_sdk/mq_request.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | class RequestBase: 5 | def __init__(self, instance_id): 6 | self.method = "" 7 | if instance_id is None: 8 | self.instance_id = "" 9 | else: 10 | self.instance_id = instance_id 11 | 12 | 13 | class ResponseBase: 14 | def __init__(self): 15 | self.status = -1 16 | self.header = {} 17 | self.error_data = "" 18 | 19 | def get_req_id(self): 20 | return self.header.get("x-mq-request-id") 21 | 22 | 23 | class PublishMessageRequest(RequestBase): 24 | def __init__(self, instance_id, topic_name, message_body, message_tag="", properties=""): 25 | RequestBase.__init__(self, instance_id) 26 | self.topic_name = topic_name 27 | self.message_body = message_body 28 | self.message_tag = message_tag 29 | self.properties = properties 30 | self.method = "POST" 31 | 32 | 33 | class PublishMessageResponse(ResponseBase): 34 | def __init__(self): 35 | ResponseBase.__init__(self) 36 | self.message_id = "" 37 | self.message_body_md5 = "" 38 | self.receipt_handle = "" 39 | 40 | 41 | class ConsumeMessageRequest(RequestBase): 42 | def __init__(self, instance_id, topic_name, consumer, batch_size, message_tag, wait_seconds=-1): 43 | RequestBase.__init__(self, instance_id) 44 | self.topic_name = topic_name 45 | self.consumer = consumer 46 | self.batch_size = batch_size 47 | self.message_tag = message_tag 48 | self.wait_seconds = wait_seconds 49 | self.method = "GET" 50 | self.trans = "" 51 | 52 | def set_trans_pop(self): 53 | self.trans = "pop" 54 | 55 | def set_order(self): 56 | self.trans = "order" 57 | 58 | 59 | class ConsumeMessageResponseEntry: 60 | def __init__(self): 61 | self.consumed_times = -1 62 | self.publish_time = -1 63 | self.first_consume_time = -1 64 | self.message_body = "" 65 | self.message_id = "" 66 | self.message_body_md5 = "" 67 | self.next_consume_time = "" 68 | self.receipt_handle = "" 69 | self.message_tag = "" 70 | self.properties = "" 71 | 72 | 73 | class ConsumeMessageResponse(ResponseBase): 74 | def __init__(self): 75 | ResponseBase.__init__(self) 76 | self.message_list = [] 77 | 78 | 79 | class AckMessageRequest(RequestBase): 80 | def __init__(self, instance_id, topic_name, consumer, receipt_handle_list): 81 | RequestBase.__init__(self, instance_id) 82 | self.topic_name = topic_name 83 | self.consumer = consumer 84 | self.receipt_handle_list = receipt_handle_list 85 | self.method = "DELETE" 86 | self.trans = "" 87 | 88 | def set_trans_commit(self): 89 | self.trans = "commit" 90 | 91 | def set_trans_rollback(self): 92 | self.trans = "rollback" 93 | 94 | 95 | class AckMessageResponse(ResponseBase): 96 | def __init__(self): 97 | ResponseBase.__init__(self) 98 | -------------------------------------------------------------------------------- /mq_http_sdk/mq_tool.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import os 4 | import logging 5 | import logging.handlers 6 | from .mq_exception import * 7 | 8 | METHODS = ["PUT", "POST", "GET", "DELETE"] 9 | 10 | 11 | class MQLogger: 12 | @staticmethod 13 | def get_logger(log_name=None, log_file=None, log_level=logging.INFO): 14 | if log_name is None: 15 | log_name = "mq_python_sdk" 16 | if log_file is None: 17 | log_file = os.path.join(os.path.split(os.path.realpath(__file__))[0], "mq_python_sdk.log") 18 | logger = logging.getLogger(log_name) 19 | if logger.handlers == []: 20 | fileHandler = logging.handlers.RotatingFileHandler(log_file, maxBytes=10 * 1024 * 1024) 21 | formatter = logging.Formatter( 22 | '[%(asctime)s] [%(name)s] [%(levelname)s] [%(filename)s:%(lineno)d] [%(thread)d] %(message)s', 23 | '%Y-%m-%d %H:%M:%S') 24 | fileHandler.setFormatter(formatter) 25 | logger.addHandler(fileHandler) 26 | MQLogger.validate_loglevel(log_level) 27 | logger.setLevel(log_level) 28 | return logger 29 | 30 | @staticmethod 31 | def validate_loglevel(log_level): 32 | log_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL] 33 | if log_level not in log_levels: 34 | raise MQClientParameterException("LogLevelInvalid", "Bad value: '%s', expect levels: '%s'." % \ 35 | (log_level, ','.join([str(item) for item in log_levels]))) 36 | 37 | 38 | class ValidatorBase: 39 | @staticmethod 40 | def validate(req): 41 | pass 42 | 43 | @staticmethod 44 | def type_validate(item, valid_type, param_name=None): 45 | if not (type(item) is valid_type): 46 | if param_name is None: 47 | raise MQClientParameterException("TypeInvalid", "Bad type: '%s', '%s' expect type '%s'." % ( 48 | type(item), item, valid_type)) 49 | else: 50 | raise MQClientParameterException("TypeInvalid", 51 | "Param '%s' in bad type: '%s', '%s' expect type '%s'." % ( 52 | param_name, type(item), item, valid_type)) 53 | 54 | @staticmethod 55 | def is_str(item, param_name=None): 56 | if not isinstance(item, str): 57 | if param_name is None: 58 | raise MQClientParameterException("TypeInvalid", 59 | "Bad type: '%s', '%s' expect basestring." % (type(item), item)) 60 | else: 61 | raise MQClientParameterException("TypeInvalid", 62 | "Param '%s' in bad type: '%s', '%s' expect basestring." % ( 63 | param_name, type(item), item)) 64 | 65 | @staticmethod 66 | def name_validate(name, nameType): 67 | # type 68 | ValidatorBase.is_str(name) 69 | 70 | # length 71 | if len(name) < 1: 72 | raise MQClientParameterException("NameInvalid", 73 | "Bad value: '%s', the length of %s should larger than 1." % ( 74 | name, nameType)) 75 | 76 | 77 | class MessageValidator(ValidatorBase): 78 | @staticmethod 79 | def receiphandle_validate(receipt_handle): 80 | if receipt_handle == "": 81 | raise MQClientParameterException("ReceiptHandleInvalid", "The receipt handle should not be null.") 82 | 83 | @staticmethod 84 | def consumer_validate(consumer): 85 | if consumer == "": 86 | raise MQClientParameterException("ConsumerInvalid", "The consumer should not be null.") 87 | 88 | @staticmethod 89 | def waitseconds_validate(wait_seconds): 90 | if wait_seconds != -1 and wait_seconds < 0: 91 | raise MQClientParameterException("WaitSecondsInvalid", 92 | "Bad value: '%d', wait_seconds should larger than 0." % wait_seconds) 93 | 94 | @staticmethod 95 | def consume_tag_validate(message_tag): 96 | if len(message_tag) > 64: 97 | raise MQClientParameterException("ConsumeTagInvalid", 98 | "The length of message tag should be between 1 and 64.") 99 | 100 | @staticmethod 101 | def batchsize_validate(batch_size): 102 | if batch_size != -1 and batch_size < 0: 103 | raise MQClientParameterException("BatchSizeInvalid", 104 | "Bad value: '%d', batch_size should larger than 0." % batch_size) 105 | 106 | @staticmethod 107 | def publishmessage_attr_validate(req): 108 | # type 109 | ValidatorBase.is_str(req.message_body, "message_body") 110 | ValidatorBase.is_str(req.message_tag, "message_tag") 111 | # value 112 | if req.message_body == "": 113 | raise MQClientParameterException("MessageBodyInvalid", "Bad value: '', message body should not be ''.") 114 | if len(req.message_tag) > 64: 115 | raise MQClientParameterException("MessageTagInvalid", 116 | "The length of message tag should be between 1 and 64.") 117 | 118 | 119 | class ConsumeMessageValidator(MessageValidator): 120 | @staticmethod 121 | def validate(req): 122 | MessageValidator.validate(req) 123 | ValidatorBase.name_validate(req.topic_name, "topic_name") 124 | ValidatorBase.name_validate(req.consumer, "consumer") 125 | MessageValidator.batchsize_validate(req.batch_size) 126 | MessageValidator.waitseconds_validate(req.wait_seconds) 127 | MessageValidator.consumer_validate(req.consumer) 128 | 129 | 130 | class AckMessageValidator(MessageValidator): 131 | @staticmethod 132 | def validate(req): 133 | MessageValidator.validate(req) 134 | ValidatorBase.name_validate(req.topic_name, "topic_name") 135 | ValidatorBase.name_validate(req.consumer, "consumer") 136 | MessageValidator.consumer_validate(req.consumer) 137 | for receipt_handle in req.receipt_handle_list: 138 | MessageValidator.receiphandle_validate(receipt_handle) 139 | 140 | 141 | class PublishMessageValidator(MessageValidator): 142 | @staticmethod 143 | def validate(req): 144 | MessageValidator.validate(req) 145 | ValidatorBase.name_validate(req.topic_name, "topic_name") 146 | MessageValidator.publishmessage_attr_validate(req) 147 | 148 | 149 | class MQUtils: 150 | def __init__(self): 151 | pass 152 | 153 | @staticmethod 154 | def check_property(prop): 155 | if ":" in prop or "|" in prop or "\"" in prop or "&" in prop or "'" in prop or "<" in prop or ">" in prop: 156 | return False 157 | 158 | return True 159 | 160 | @staticmethod 161 | def map_to_string(properties): 162 | if properties is None: 163 | return "" 164 | ret = "" 165 | for key, value in list(properties.items()): 166 | if MQUtils.check_property(key) is False or MQUtils.check_property(value) is False: 167 | raise MQClientParameterException("MessagePropertyInvalid", "Message's property['%s':'%s'] ] can't " 168 | "contains: & \" ' < > : |" % (key, value)) 169 | ret += key + ":" + value + "|" 170 | return ret 171 | 172 | @staticmethod 173 | def string_to_map(property_str): 174 | if property_str is None or property_str == "": 175 | return {} 176 | 177 | kv_array = property_str.split("|") 178 | properties = {} 179 | for kv in kv_array: 180 | if kv is None or kv == "" or ":" not in kv: 181 | continue 182 | k_and_v = list(kv.split(":")) 183 | if len(k_and_v) != 2: 184 | continue 185 | if k_and_v[0] != "" and k_and_v[1] != "": 186 | try: 187 | properties[str(k_and_v[0])] = str(k_and_v[1]) 188 | except UnicodeEncodeError: 189 | properties[k_and_v[0].encode('utf-8')] = k_and_v[1].encode('utf-8') 190 | return properties 191 | -------------------------------------------------------------------------------- /mq_http_sdk/mq_xml_handler.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import xml.dom.minidom 4 | import sys 5 | from .mq_exception import * 6 | from .mq_request import * 7 | 8 | try: 9 | import json 10 | except ImportError as e: 11 | import simplejson as json 12 | 13 | XMLNS = "http://mq.aliyuncs.com/doc/v1/" 14 | 15 | 16 | class EncoderBase: 17 | @staticmethod 18 | def insert_if_valid(item_name, item_value, invalid_value, data_dic): 19 | if item_value != invalid_value: 20 | data_dic[item_name] = item_value 21 | 22 | @staticmethod 23 | def list_to_xml(tag_name1, tag_name2, data_list): 24 | doc = xml.dom.minidom.Document() 25 | rootNode = doc.createElement(tag_name1) 26 | rootNode.attributes["xmlns"] = XMLNS 27 | doc.appendChild(rootNode) 28 | if data_list: 29 | for item in data_list: 30 | keyNode = doc.createElement(tag_name2) 31 | rootNode.appendChild(keyNode) 32 | keyNode.appendChild(doc.createTextNode(item)) 33 | else: 34 | nullNode = doc.createTextNode("") 35 | rootNode.appendChild(nullNode) 36 | return doc.toxml("utf-8") 37 | 38 | @staticmethod 39 | def dic_to_xml(tag_name, data_dic): 40 | doc = xml.dom.minidom.Document() 41 | rootNode = doc.createElement(tag_name) 42 | rootNode.attributes["xmlns"] = XMLNS 43 | doc.appendChild(rootNode) 44 | if data_dic: 45 | for k, v in list(data_dic.items()): 46 | keyNode = doc.createElement(k) 47 | if type(v) is dict: 48 | for subkey, subv in list(v.items()): 49 | subNode = doc.createElement(subkey) 50 | subNode.appendChild(doc.createTextNode(subv)) 51 | keyNode.appendChild(subNode) 52 | else: 53 | keyNode.appendChild(doc.createTextNode(v)) 54 | rootNode.appendChild(keyNode) 55 | else: 56 | nullNode = doc.createTextNode("") 57 | rootNode.appendChild(nullNode) 58 | return doc.toxml("utf-8") 59 | 60 | 61 | class TopicMessageEncoder: 62 | @staticmethod 63 | def encode(req): 64 | message = {} 65 | # xml only support unicode when contains Chinese 66 | if sys.version > '3': 67 | EncoderBase.insert_if_valid("MessageBody", req.message_body, "", message) 68 | EncoderBase.insert_if_valid("Properties", req.properties, "", message) 69 | else: 70 | msgbody = req.message_body.decode('utf-8') if isinstance(req.message_body, str) else req.message_body 71 | EncoderBase.insert_if_valid("MessageBody", msgbody, "", message) 72 | msgprops = req.properties.decode('utf-8') if isinstance(req.properties, str) else req.properties 73 | EncoderBase.insert_if_valid("Properties", msgprops, "", message) 74 | EncoderBase.insert_if_valid("MessageTag", req.message_tag, "", message) 75 | return EncoderBase.dic_to_xml("Message", message) 76 | 77 | 78 | class ReceiptHandlesEncoder: 79 | @staticmethod 80 | def encode(receipt_handle_list): 81 | return EncoderBase.list_to_xml("ReceiptHandles", "ReceiptHandle", receipt_handle_list) 82 | 83 | 84 | # -------------------------------------------------decode-----------------------------------------------------# 85 | class DecoderBase: 86 | @staticmethod 87 | def xml_to_nodes(tag_name, xml_data): 88 | if xml_data == "": 89 | raise MQClientNetworkException("RespDataDamaged", "Xml data is \"\"!") 90 | 91 | try: 92 | dom = xml.dom.minidom.parseString(xml_data) 93 | except Exception as e: 94 | raise MQClientNetworkException("RespDataDamaged", xml_data) 95 | 96 | nodelist = dom.getElementsByTagName(tag_name) 97 | if not nodelist: 98 | raise MQClientNetworkException("RespDataDamaged", 99 | "No element with tag name '%s'.\nData:%s" % (tag_name, xml_data)) 100 | 101 | return nodelist[0].childNodes 102 | 103 | @staticmethod 104 | def xml_to_dic(tag_name, xml_data, data_dic, req_id=None): 105 | try: 106 | for node in DecoderBase.xml_to_nodes(tag_name, xml_data): 107 | if node.nodeName != "#text": 108 | if node.childNodes != []: 109 | data_dic[node.nodeName] = node.firstChild.data 110 | else: 111 | data_dic[node.nodeName] = "" 112 | except MQClientNetworkException as e: 113 | raise MQClientNetworkException(e.type, e.message, req_id) 114 | 115 | @staticmethod 116 | def xml_to_listofdic(root_tagname, sec_tagname, xml_data, data_listofdic, req_id=None): 117 | try: 118 | for message in DecoderBase.xml_to_nodes(root_tagname, xml_data): 119 | if message.nodeName != sec_tagname: 120 | continue 121 | 122 | data_dic = {} 123 | for property in message.childNodes: 124 | if property.nodeName != "#text" and property.childNodes != []: 125 | data_dic[property.nodeName] = property.firstChild.data 126 | data_listofdic.append(data_dic) 127 | except MQClientNetworkException as e: 128 | raise MQClientNetworkException(e.type, e.message, req_id) 129 | 130 | 131 | class ConsumeMessageDecoder(DecoderBase): 132 | @staticmethod 133 | def decode(xml_data, req_id=None): 134 | data_listofdic = [] 135 | message_list = [] 136 | DecoderBase.xml_to_listofdic("Messages", "Message", xml_data, data_listofdic, req_id) 137 | try: 138 | for data_dic in data_listofdic: 139 | msg = ConsumeMessageResponseEntry() 140 | msg.message_body = data_dic["MessageBody"] 141 | msg.consumed_times = int(data_dic["ConsumedTimes"]) 142 | msg.publish_time = int(data_dic["PublishTime"]) 143 | msg.first_consume_time = int(data_dic["FirstConsumeTime"]) 144 | msg.message_id = data_dic["MessageId"] 145 | if "MessageBodyMD5" in data_dic: 146 | msg.message_body_md5 = data_dic["MessageBodyMD5"] 147 | msg.next_consume_time = int(data_dic["NextConsumeTime"]) 148 | msg.receipt_handle = data_dic["ReceiptHandle"] 149 | if "MessageTag" in data_dic: 150 | msg.message_tag = data_dic["MessageTag"] 151 | if "Properties" in data_dic: 152 | msg.properties = data_dic["Properties"] 153 | message_list.append(msg) 154 | except Exception as e: 155 | raise MQClientNetworkException("RespDataDamaged", xml_data, req_id) 156 | return message_list 157 | 158 | 159 | class AckMessageDecoder(DecoderBase): 160 | @staticmethod 161 | def decodeError(xml_data, req_id=None): 162 | try: 163 | return ErrorDecoder.decodeError(xml_data, req_id) 164 | except Exception as e: 165 | pass 166 | 167 | data_listofdic = [] 168 | DecoderBase.xml_to_listofdic("Errors", "Error", xml_data, data_listofdic, req_id) 169 | if len(data_listofdic) == 0: 170 | raise MQClientNetworkException("RespDataDamaged", xml_data, req_id) 171 | 172 | key_list = sorted(["ErrorCode", "ErrorMessage", "ReceiptHandle"]) 173 | for data_dic in data_listofdic: 174 | for key in key_list: 175 | keys = sorted(data_dic.keys()) 176 | if keys != key_list: 177 | raise MQClientNetworkException("RespDataDamaged", xml_data, req_id) 178 | return data_listofdic[0]["ErrorCode"], data_listofdic[0]["ErrorMessage"], None, None, data_listofdic 179 | 180 | 181 | class PublishMessageDecoder(DecoderBase): 182 | @staticmethod 183 | def decode(xml_data, req_id=None): 184 | data_dic = {} 185 | DecoderBase.xml_to_dic("Message", xml_data, data_dic, req_id) 186 | key_list = ["MessageId", "MessageBodyMD5"] 187 | for key in key_list: 188 | if key not in list(data_dic.keys()): 189 | raise MQClientNetworkException("RespDataDamaged", xml_data, req_id) 190 | 191 | if "ReceiptHandle" in data_dic: 192 | return data_dic["MessageId"], data_dic["MessageBodyMD5"], data_dic["ReceiptHandle"] 193 | 194 | return data_dic["MessageId"], data_dic["MessageBodyMD5"], "" 195 | 196 | 197 | class ErrorDecoder(DecoderBase): 198 | @staticmethod 199 | def decodeError(xml_data, req_id=None): 200 | data_dic = {} 201 | DecoderBase.xml_to_dic("Error", xml_data, data_dic, req_id) 202 | key_list = ["Code", "Message", "RequestId", "HostId"] 203 | for key in key_list: 204 | if key not in list(data_dic.keys()): 205 | raise MQClientNetworkException("RespDataDamaged", xml_data, req_id) 206 | return data_dic["Code"], data_dic["Message"], data_dic["RequestId"], data_dic["HostId"], None 207 | -------------------------------------------------------------------------------- /mq_http_sdk/pkg_info.py: -------------------------------------------------------------------------------- 1 | name = "mq_http_sdk" 2 | version = "1.0.3" 3 | url = "https://github.com/aliyunmq/mq-http-python-sdk" 4 | license = "MIT" 5 | short_description = "Aliyun Message Queue(MQ) Http Python SDK" 6 | long_description = """ 7 | Provides http interfaces to Aliyun Message Queue(MQ) Service. 8 | """ 9 | -------------------------------------------------------------------------------- /setup.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliyunmq/mq-http-python-sdk/35a2bef791abb8bb57f9ae2ad1083f1de62a7bb0/setup.conf -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | try: 4 | from setuptools import setup 5 | except ImportError: 6 | from distutils.core import setup 7 | 8 | import sys 9 | import mq_http_sdk.pkg_info 10 | 11 | if sys.version_info <= (2, 5): 12 | sys.stderr.write("ERROR: mq python sdk requires Python Version 2.5 or above.\n") 13 | sys.stderr.write("Your Python version is %s.%s.%s.\n" % sys.version_info[:3]) 14 | sys.exit(1) 15 | 16 | setup(name=mq_http_sdk.pkg_info.name, 17 | version=mq_http_sdk.pkg_info.version, 18 | author="aliyunmq", 19 | author_email="", 20 | url=mq_http_sdk.pkg_info.url, 21 | packages=["mq_http_sdk"], 22 | license=mq_http_sdk.pkg_info.license, 23 | description=mq_http_sdk.pkg_info.short_description, 24 | long_description=mq_http_sdk.pkg_info.long_description) 25 | --------------------------------------------------------------------------------