|
@@ -0,0 +1,324 @@
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+'''
|
|
|
+Created on 2018-9-17
|
|
|
+
|
|
|
+@author: xiaoxuan.lp
|
|
|
+'''
|
|
|
+
|
|
|
+try: import httplib
|
|
|
+except ImportError:
|
|
|
+ import http.client as httplib
|
|
|
+import urllib
|
|
|
+import time
|
|
|
+import hashlib
|
|
|
+import json
|
|
|
+import dingtalk
|
|
|
+import itertools
|
|
|
+import mimetypes
|
|
|
+import hmac
|
|
|
+import base64
|
|
|
+
|
|
|
+'''
|
|
|
+定义一些系统变量
|
|
|
+'''
|
|
|
+
|
|
|
+SYSTEM_GENERATE_VERSION = "taobao-sdk-python-dynamicVersionNo"
|
|
|
+
|
|
|
+P_APPKEY = "app_key"
|
|
|
+P_API = "method"
|
|
|
+P_ACCESS_TOKEN = "access_token"
|
|
|
+P_VERSION = "v"
|
|
|
+P_FORMAT = "format"
|
|
|
+P_TIMESTAMP = "timestamp"
|
|
|
+P_SIGN = "sign"
|
|
|
+P_SIGN_METHOD = "sign_method"
|
|
|
+P_PARTNER_ID = "partner_id"
|
|
|
+
|
|
|
+P_CODE = 'errcode'
|
|
|
+P_MSG = 'errmsg'
|
|
|
+
|
|
|
+def sign(secret, parameters):
|
|
|
+ #===========================================================================
|
|
|
+ # '''签名方法
|
|
|
+ # @param secret: 签名需要的密钥
|
|
|
+ # @param parameters: 支持字典和string两种
|
|
|
+ # '''
|
|
|
+ #===========================================================================
|
|
|
+ # 如果parameters 是字典类的话
|
|
|
+ if hasattr(parameters, "items"):
|
|
|
+ keys = parameters.keys()
|
|
|
+ keys.sort()
|
|
|
+
|
|
|
+ parameters = "%s%s%s" % (secret,
|
|
|
+ str().join('%s%s' % (key, parameters[key]) for key in keys),
|
|
|
+ secret)
|
|
|
+ sign = hashlib.md5(parameters).hexdigest().upper()
|
|
|
+ return sign
|
|
|
+
|
|
|
+def mixStr(pstr):
|
|
|
+ if(isinstance(pstr, str)):
|
|
|
+ return pstr
|
|
|
+ elif(isinstance(pstr, unicode)):
|
|
|
+ return pstr.encode('utf-8')
|
|
|
+ else:
|
|
|
+ return str(pstr)
|
|
|
+
|
|
|
+class FileItem(object):
|
|
|
+ def __init__(self,filename=None,content=None):
|
|
|
+ self.filename = filename
|
|
|
+ self.content = content
|
|
|
+
|
|
|
+class MultiPartForm(object):
|
|
|
+ """Accumulate the data to be used when posting a form."""
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self.form_fields = []
|
|
|
+ self.files = []
|
|
|
+ self.boundary = "PYTHON_SDK_BOUNDARY"
|
|
|
+ return
|
|
|
+
|
|
|
+ def get_content_type(self):
|
|
|
+ return 'multipart/form-data;charset=UTF-8; boundary=%s' % self.boundary
|
|
|
+
|
|
|
+ def add_field(self, name, value):
|
|
|
+ """Add a simple field to the form data."""
|
|
|
+ self.form_fields.append((name, str(value)))
|
|
|
+ return
|
|
|
+
|
|
|
+ def add_file(self, fieldname, filename, fileHandle, mimetype=None):
|
|
|
+ """Add a file to be uploaded."""
|
|
|
+ body = fileHandle.read()
|
|
|
+ if mimetype is None:
|
|
|
+ mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
|
|
+ self.files.append((mixStr(fieldname), mixStr(filename), mixStr(mimetype), mixStr(body)))
|
|
|
+ return
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ """Return a string representing the form data, including attached files."""
|
|
|
+ # Build a list of lists, each containing "lines" of the
|
|
|
+ # request. Each part is separated by a boundary string.
|
|
|
+ # Once the list is built, return a string where each
|
|
|
+ # line is separated by '\r\n'.
|
|
|
+ parts = []
|
|
|
+ part_boundary = '--' + self.boundary
|
|
|
+
|
|
|
+ # Add the form fields
|
|
|
+ parts.extend(
|
|
|
+ [ part_boundary,
|
|
|
+ 'Content-Disposition: form-data; name="%s"' % name,
|
|
|
+ 'Content-Type: text/plain; charset=UTF-8',
|
|
|
+ '',
|
|
|
+ value,
|
|
|
+ ]
|
|
|
+ for name, value in self.form_fields
|
|
|
+ )
|
|
|
+
|
|
|
+ # Add the files to upload
|
|
|
+ parts.extend(
|
|
|
+ [ part_boundary,
|
|
|
+ 'Content-Disposition: form-data; name="%s"; filename="%s"' % \
|
|
|
+ (field_name, filename),
|
|
|
+ 'Content-Type: %s' % content_type,
|
|
|
+ 'Content-Transfer-Encoding: binary',
|
|
|
+ '',
|
|
|
+ body,
|
|
|
+ ]
|
|
|
+ for field_name, filename, content_type, body in self.files
|
|
|
+ )
|
|
|
+
|
|
|
+ # Flatten the list and add closing boundary marker,
|
|
|
+ # then return CR+LF separated data
|
|
|
+ flattened = list(itertools.chain(*parts))
|
|
|
+ flattened.append('--' + self.boundary + '--')
|
|
|
+ flattened.append('')
|
|
|
+ return '\r\n'.join(flattened)
|
|
|
+
|
|
|
+class TopException(Exception):
|
|
|
+ #===========================================================================
|
|
|
+ # 业务异常类
|
|
|
+ #===========================================================================
|
|
|
+ def __init__(self):
|
|
|
+ self.errcode = None
|
|
|
+ self.errmsg = None
|
|
|
+ self.application_host = None
|
|
|
+ self.service_host = None
|
|
|
+
|
|
|
+ def __str__(self, *args, **kwargs):
|
|
|
+ sb = "errcode=" + mixStr(self.errcode) +\
|
|
|
+ " errmsg=" + mixStr(self.errmsg) +\
|
|
|
+ " application_host=" + mixStr(self.application_host) +\
|
|
|
+ " service_host=" + mixStr(self.service_host)
|
|
|
+ return sb
|
|
|
+
|
|
|
+class RequestException(Exception):
|
|
|
+ #===========================================================================
|
|
|
+ # 请求连接异常类
|
|
|
+ #===========================================================================
|
|
|
+ pass
|
|
|
+
|
|
|
+class RestApi(object):
|
|
|
+ #===========================================================================
|
|
|
+ # Rest api的基类
|
|
|
+ #===========================================================================
|
|
|
+
|
|
|
+ def __init__(self, url=None):
|
|
|
+ #=======================================================================
|
|
|
+ # 初始化基类
|
|
|
+ # Args @param domain: 请求的域名或者ip
|
|
|
+ # @param port: 请求的端口
|
|
|
+ #=======================================================================
|
|
|
+ if(url == None):
|
|
|
+ raise RequestException("domain must not be empty.")
|
|
|
+ if(url.find('http://') >= 0):
|
|
|
+ self.__port = 80
|
|
|
+ pathUrl = url.replace('http://','')
|
|
|
+ elif(url.find('https://') >= 0):
|
|
|
+ self.__port = 443
|
|
|
+ pathUrl = url.replace('https://','')
|
|
|
+ else:
|
|
|
+ raise RequestException("http protocol is not validate.")
|
|
|
+
|
|
|
+ index = pathUrl.find('/')
|
|
|
+ if(index > 0):
|
|
|
+ self.__domain = pathUrl[0:index]
|
|
|
+ self.__path = pathUrl[index:]
|
|
|
+ else:
|
|
|
+ self.__domain = pathUrl
|
|
|
+ self.__path = ''
|
|
|
+
|
|
|
+ # print("domain:" + self.__domain + ",path:" + self.__path + ",port:" + str(self.__port))
|
|
|
+
|
|
|
+ def get_request_header(self):
|
|
|
+ return {
|
|
|
+ 'Content-type': 'application/json;charset=UTF-8',
|
|
|
+ "Cache-Control": "no-cache",
|
|
|
+ "Connection": "Keep-Alive",
|
|
|
+ }
|
|
|
+
|
|
|
+ def getHttpMethod(self):
|
|
|
+ return "GET"
|
|
|
+
|
|
|
+ def getapiname(self):
|
|
|
+ return ""
|
|
|
+
|
|
|
+ def getMultipartParas(self):
|
|
|
+ return [];
|
|
|
+
|
|
|
+ def getTranslateParas(self):
|
|
|
+ return {};
|
|
|
+
|
|
|
+ def _check_requst(self):
|
|
|
+ pass
|
|
|
+
|
|
|
+ def getResponse(self, authrize='',accessKey='',accessSecret='',suiteTicket='',corpId='', timeout=30):
|
|
|
+ #=======================================================================
|
|
|
+ # 获取response结果
|
|
|
+ #=======================================================================
|
|
|
+ if(self.__port == 443):
|
|
|
+ connection = httplib.HTTPSConnection(self.__domain, self.__port, None, None, False, timeout)
|
|
|
+ else:
|
|
|
+ connection = httplib.HTTPConnection(self.__domain, self.__port, False, timeout)
|
|
|
+ sys_parameters = {
|
|
|
+ P_PARTNER_ID: SYSTEM_GENERATE_VERSION,
|
|
|
+ }
|
|
|
+ if authrize is not None:
|
|
|
+ sys_parameters[P_ACCESS_TOKEN] = authrize
|
|
|
+ application_parameter = self.getApplicationParameters()
|
|
|
+ sign_parameter = sys_parameters.copy()
|
|
|
+ sign_parameter.update(application_parameter)
|
|
|
+
|
|
|
+ header = self.get_request_header();
|
|
|
+ if(self.getMultipartParas()):
|
|
|
+ form = MultiPartForm()
|
|
|
+ for key, value in application_parameter.items():
|
|
|
+ form.add_field(key, value)
|
|
|
+ for key in self.getMultipartParas():
|
|
|
+ fileitem = getattr(self,key)
|
|
|
+ if(fileitem and isinstance(fileitem,FileItem)):
|
|
|
+ form.add_file(key,fileitem.filename,fileitem.content)
|
|
|
+ body = str(form)
|
|
|
+ header['Content-type'] = form.get_content_type()
|
|
|
+ else:
|
|
|
+ body = urllib.urlencode(application_parameter)
|
|
|
+
|
|
|
+ if(accessKey != ''):
|
|
|
+ timestamp = str(int(round(time.time()))) + '000'
|
|
|
+ print("timestamp:" + timestamp)
|
|
|
+ canonicalString = self.getCanonicalStringForIsv(timestamp, suiteTicket)
|
|
|
+ print("canonicalString:" + canonicalString)
|
|
|
+ print("accessSecret:" + accessSecret)
|
|
|
+ signature = self.computeSignature(accessSecret, canonicalString)
|
|
|
+ print("signature:" + signature)
|
|
|
+ ps = {}
|
|
|
+ ps["accessKey"] = accessKey
|
|
|
+ ps["signature"] = signature
|
|
|
+ ps["timestamp"] = timestamp
|
|
|
+ if(suiteTicket != ''):
|
|
|
+ ps["suiteTicket"] = suiteTicket
|
|
|
+ if(corpId != ''):
|
|
|
+ ps["corpId"] = corpId
|
|
|
+ queryStr = urllib.urlencode(ps)
|
|
|
+ if (self.__path.find("?") > 0):
|
|
|
+ fullPath = self.__path + "&" + queryStr
|
|
|
+ else:
|
|
|
+ fullPath = self.__path + "?" + queryStr
|
|
|
+ print("fullPath:" + fullPath)
|
|
|
+ else:
|
|
|
+ if (self.__path.find("?") > 0):
|
|
|
+ fullPath = (self.__path + "&access_token=" + str(authrize)) if len(str(authrize)) > 0 else self.__path
|
|
|
+ else:
|
|
|
+ fullPath = (self.__path + "?access_token=" + str(authrize)) if len(str(authrize)) > 0 else self.__path
|
|
|
+
|
|
|
+ if(self.getHttpMethod() == "GET"):
|
|
|
+ if (fullPath.find("?") > 0):
|
|
|
+ fullPath = fullPath + "&" + body;
|
|
|
+ else:
|
|
|
+ fullPath = fullPath + "?" + body
|
|
|
+ connection.request(self.getHttpMethod(), fullPath, headers=header)
|
|
|
+ else:
|
|
|
+ if (self.getMultipartParas()):
|
|
|
+ body = body
|
|
|
+ else:
|
|
|
+ body = json.dumps(application_parameter)
|
|
|
+ connection.request(self.getHttpMethod(), fullPath, body=body, headers=header)
|
|
|
+ response = connection.getresponse();
|
|
|
+ if response.status is not 200:
|
|
|
+ raise RequestException('invalid http status ' + str(response.status) + ',detail body:' + response.read())
|
|
|
+ result = response.read()
|
|
|
+ # print("result:" + result)
|
|
|
+ jsonobj = json.loads(result)
|
|
|
+ if jsonobj.has_key(P_CODE) and jsonobj[P_CODE] != 0:
|
|
|
+ error = TopException()
|
|
|
+ error.errcode = jsonobj[P_CODE]
|
|
|
+ error.errmsg = jsonobj[P_MSG]
|
|
|
+ error.application_host = response.getheader("Application-Host", "")
|
|
|
+ error.service_host = response.getheader("Location-Host", "")
|
|
|
+ raise error
|
|
|
+ return jsonobj
|
|
|
+
|
|
|
+ def getCanonicalStringForIsv(self, timestamp, suiteTicket):
|
|
|
+ if(suiteTicket != ''):
|
|
|
+ return timestamp + '\n' + suiteTicket
|
|
|
+ else:
|
|
|
+ return timestamp
|
|
|
+
|
|
|
+ def computeSignature(self, secret, canonicalString):
|
|
|
+ message = canonicalString.encode(encoding="utf-8")
|
|
|
+ sec = secret.encode(encoding="utf-8")
|
|
|
+ return str(base64.b64encode(hmac.new(sec, message, digestmod=hashlib.sha256).digest()))
|
|
|
+
|
|
|
+ def getApplicationParameters(self):
|
|
|
+ application_parameter = {}
|
|
|
+ for key, value in self.__dict__.iteritems():
|
|
|
+ if not key.startswith("__") and not key in self.getMultipartParas() and not key.startswith("_RestApi__") and value is not None :
|
|
|
+ if(key.startswith("_")):
|
|
|
+ application_parameter[key[1:]] = value
|
|
|
+ else:
|
|
|
+ application_parameter[key] = value
|
|
|
+ #查询翻译字典来规避一些关键字属性
|
|
|
+ translate_parameter = self.getTranslateParas()
|
|
|
+ for key, value in application_parameter.iteritems():
|
|
|
+ if key in translate_parameter:
|
|
|
+ application_parameter[translate_parameter[key]] = application_parameter[key]
|
|
|
+ del application_parameter[key]
|
|
|
+ return application_parameter
|