├── .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 |
--------------------------------------------------------------------------------