您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

api_utils.py 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. #
  2. # Copyright 2019 The FATE Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. import json
  17. import random
  18. import time
  19. from functools import wraps
  20. from io import BytesIO
  21. from flask import (
  22. Response, jsonify, send_file,make_response,
  23. request as flask_request,
  24. )
  25. from werkzeug.http import HTTP_STATUS_CODES
  26. from web_server.utils import json_dumps
  27. from web_server.versions import get_fate_version
  28. from web_server.settings import RetCode
  29. from web_server.settings import (
  30. REQUEST_MAX_WAIT_SEC, REQUEST_WAIT_SEC,
  31. stat_logger,CLIENT_AUTHENTICATION, HTTP_APP_KEY, SECRET_KEY
  32. )
  33. import requests
  34. import functools
  35. from web_server.utils import CustomJSONEncoder
  36. from uuid import uuid1
  37. from base64 import b64encode
  38. from hmac import HMAC
  39. from urllib.parse import quote, urlencode
  40. requests.models.complexjson.dumps = functools.partial(json.dumps, cls=CustomJSONEncoder)
  41. def request(**kwargs):
  42. sess = requests.Session()
  43. stream = kwargs.pop('stream', sess.stream)
  44. timeout = kwargs.pop('timeout', None)
  45. kwargs['headers'] = {k.replace('_', '-').upper(): v for k, v in kwargs.get('headers', {}).items()}
  46. prepped = requests.Request(**kwargs).prepare()
  47. if CLIENT_AUTHENTICATION and HTTP_APP_KEY and SECRET_KEY:
  48. timestamp = str(round(time() * 1000))
  49. nonce = str(uuid1())
  50. signature = b64encode(HMAC(SECRET_KEY.encode('ascii'), b'\n'.join([
  51. timestamp.encode('ascii'),
  52. nonce.encode('ascii'),
  53. HTTP_APP_KEY.encode('ascii'),
  54. prepped.path_url.encode('ascii'),
  55. prepped.body if kwargs.get('json') else b'',
  56. urlencode(sorted(kwargs['data'].items()), quote_via=quote, safe='-._~').encode('ascii')
  57. if kwargs.get('data') and isinstance(kwargs['data'], dict) else b'',
  58. ]), 'sha1').digest()).decode('ascii')
  59. prepped.headers.update({
  60. 'TIMESTAMP': timestamp,
  61. 'NONCE': nonce,
  62. 'APP-KEY': HTTP_APP_KEY,
  63. 'SIGNATURE': signature,
  64. })
  65. return sess.send(prepped, stream=stream, timeout=timeout)
  66. fate_version = get_fate_version() or ''
  67. def get_exponential_backoff_interval(retries, full_jitter=False):
  68. """Calculate the exponential backoff wait time."""
  69. # Will be zero if factor equals 0
  70. countdown = min(REQUEST_MAX_WAIT_SEC, REQUEST_WAIT_SEC * (2 ** retries))
  71. # Full jitter according to
  72. # https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
  73. if full_jitter:
  74. countdown = random.randrange(countdown + 1)
  75. # Adjust according to maximum wait time and account for negative values.
  76. return max(0, countdown)
  77. def get_json_result(retcode=RetCode.SUCCESS, retmsg='success', data=None, job_id=None, meta=None):
  78. import re
  79. result_dict = {
  80. "retcode": retcode,
  81. "retmsg":retmsg,
  82. # "retmsg": re.sub(r"fate", "seceum", retmsg, flags=re.IGNORECASE),
  83. "data": data,
  84. "jobId": job_id,
  85. "meta": meta,
  86. }
  87. response = {}
  88. for key, value in result_dict.items():
  89. if value is None and key != "retcode":
  90. continue
  91. else:
  92. response[key] = value
  93. return jsonify(response)
  94. def get_data_error_result(retcode=RetCode.DATA_ERROR, retmsg='Sorry! Data missing!'):
  95. import re
  96. result_dict = {"retcode": retcode, "retmsg": re.sub(r"fate", "seceum", retmsg, flags=re.IGNORECASE)}
  97. response = {}
  98. for key, value in result_dict.items():
  99. if value is None and key != "retcode":
  100. continue
  101. else:
  102. response[key] = value
  103. return jsonify(response)
  104. def server_error_response(e):
  105. stat_logger.exception(e)
  106. try:
  107. if e.code==401:
  108. return get_json_result(retcode=401, retmsg=repr(e))
  109. except:
  110. pass
  111. if len(e.args) > 1:
  112. return get_json_result(retcode=RetCode.EXCEPTION_ERROR, retmsg=repr(e.args[0]), data=e.args[1])
  113. return get_json_result(retcode=RetCode.EXCEPTION_ERROR, retmsg=repr(e))
  114. def error_response(response_code, retmsg=None):
  115. if retmsg is None:
  116. retmsg = HTTP_STATUS_CODES.get(response_code, 'Unknown Error')
  117. return Response(json.dumps({
  118. 'retmsg': retmsg,
  119. 'retcode': response_code,
  120. }), status=response_code, mimetype='application/json')
  121. def validate_request(*args, **kwargs):
  122. def wrapper(func):
  123. @wraps(func)
  124. def decorated_function(*_args, **_kwargs):
  125. input_arguments = flask_request.json or flask_request.form.to_dict()
  126. no_arguments = []
  127. error_arguments = []
  128. for arg in args:
  129. if arg not in input_arguments:
  130. no_arguments.append(arg)
  131. for k, v in kwargs.items():
  132. config_value = input_arguments.get(k, None)
  133. if config_value is None:
  134. no_arguments.append(k)
  135. elif isinstance(v, (tuple, list)):
  136. if config_value not in v:
  137. error_arguments.append((k, set(v)))
  138. elif config_value != v:
  139. error_arguments.append((k, v))
  140. if no_arguments or error_arguments:
  141. error_string = ""
  142. if no_arguments:
  143. error_string += "required argument are missing: {}; ".format(",".join(no_arguments))
  144. if error_arguments:
  145. error_string += "required argument values: {}".format(",".join(["{}={}".format(a[0], a[1]) for a in error_arguments]))
  146. return get_json_result(retcode=RetCode.ARGUMENT_ERROR, retmsg=error_string)
  147. return func(*_args, **_kwargs)
  148. return decorated_function
  149. return wrapper
  150. def is_localhost(ip):
  151. return ip in {'127.0.0.1', '::1', '[::1]', 'localhost'}
  152. def send_file_in_mem(data, filename):
  153. if not isinstance(data, (str, bytes)):
  154. data = json_dumps(data)
  155. if isinstance(data, str):
  156. data = data.encode('utf-8')
  157. f = BytesIO()
  158. f.write(data)
  159. f.seek(0)
  160. return send_file(f, as_attachment=True, attachment_filename=filename)
  161. def get_json_result(retcode=RetCode.SUCCESS, retmsg='success', data=None):
  162. response = {"retcode": retcode, "retmsg": retmsg, "data": data}
  163. return jsonify(response)
  164. def cors_reponse(retcode=RetCode.SUCCESS, retmsg='success', data=None, auth=None):
  165. result_dict = {"retcode": retcode, "retmsg": retmsg, "data": data}
  166. response_dict = {}
  167. for key, value in result_dict.items():
  168. if value is None and key != "retcode":
  169. continue
  170. else:
  171. response_dict[key] = value
  172. response = make_response(jsonify(response_dict))
  173. if auth:
  174. response.headers["Authorization"] = auth
  175. response.headers["Access-Control-Allow-Origin"] = "*"
  176. response.headers["Access-Control-Allow-Method"] = "*"
  177. response.headers["Access-Control-Allow-Headers"] = "*"
  178. response.headers["Access-Control-Allow-Headers"] = "*"
  179. response.headers["Access-Control-Expose-Headers"] = "Authorization"
  180. return response