You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

134 lines
5.2 KiB

  1. """HTTP client library"""
  2. import nilmdb
  3. import nilmdb.utils
  4. from nilmdb.client.errors import ClientError, ServerError, Error
  5. import simplejson as json
  6. import urlparse
  7. import requests
  8. class HTTPClient(object):
  9. """Class to manage and perform HTTP requests from the client"""
  10. def __init__(self, baseurl = "", post_json = False):
  11. """If baseurl is supplied, all other functions that take
  12. a URL can be given a relative URL instead."""
  13. # Verify / clean up URL
  14. reparsed = urlparse.urlparse(baseurl).geturl()
  15. if '://' not in reparsed:
  16. reparsed = urlparse.urlparse("http://" + baseurl).geturl()
  17. self.baseurl = reparsed
  18. # Build Requests session object, enable SSL verification
  19. self.session = requests.Session()
  20. self.session.verify = True
  21. # Saved response, so that tests can verify a few things.
  22. self._last_response = {}
  23. # Whether to send application/json POST bodies (versus
  24. # x-www-form-urlencoded)
  25. self.post_json = post_json
  26. def _handle_error(self, url, code, body):
  27. # Default variables for exception. We use the entire body as
  28. # the default message, in case we can't extract it from a JSON
  29. # response.
  30. args = { "url" : url,
  31. "status" : str(code),
  32. "message" : body,
  33. "traceback" : None }
  34. try:
  35. # Fill with server-provided data if we can
  36. jsonerror = json.loads(body)
  37. args["status"] = jsonerror["status"]
  38. args["message"] = jsonerror["message"]
  39. args["traceback"] = jsonerror["traceback"]
  40. except Exception: # pragma: no cover
  41. pass
  42. if code >= 400 and code <= 499:
  43. raise ClientError(**args)
  44. else: # pragma: no cover
  45. if code >= 500 and code <= 599:
  46. if args["message"] is None:
  47. args["message"] = ("(no message; try disabling " +
  48. "response.stream option in " +
  49. "nilmdb.server for better debugging)")
  50. raise ServerError(**args)
  51. else:
  52. raise Error(**args)
  53. def close(self):
  54. self.session.close()
  55. def _do_req(self, method, url, query_data, body_data, stream, headers):
  56. url = urlparse.urljoin(self.baseurl, url)
  57. try:
  58. response = self.session.request(method, url,
  59. params = query_data,
  60. data = body_data,
  61. stream = stream,
  62. headers = headers)
  63. except requests.RequestException as e:
  64. raise ServerError(status = "502 Error", url = url,
  65. message = str(e.message))
  66. if response.status_code != 200:
  67. self._handle_error(url, response.status_code, response.content)
  68. self._last_response = response
  69. if response.headers["content-type"] in ("application/json",
  70. "application/x-json-stream"):
  71. return (response, True)
  72. else:
  73. return (response, False)
  74. # Normal versions that return data directly
  75. def _req(self, method, url, query = None, body = None, headers = None):
  76. """
  77. Make a request and return the body data as a string or parsed
  78. JSON object, or raise an error if it contained an error.
  79. """
  80. (response, isjson) = self._do_req(method, url, query, body,
  81. stream = False, headers = headers)
  82. if isjson:
  83. return json.loads(response.content)
  84. return response.content
  85. def get(self, url, params = None):
  86. """Simple GET (parameters in URL)"""
  87. return self._req("GET", url, params, None)
  88. def post(self, url, params = None):
  89. """Simple POST (parameters in body)"""
  90. if self.post_json:
  91. return self._req("POST", url, None,
  92. json.dumps(params),
  93. { 'Content-type': 'application/json' })
  94. else:
  95. return self._req("POST", url, None, params)
  96. def put(self, url, data, params = None):
  97. """Simple PUT (parameters in URL, data in body)"""
  98. return self._req("PUT", url, params, data)
  99. # Generator versions that return data one line at a time.
  100. def _req_gen(self, method, url, query = None, body = None, headers = None):
  101. """
  102. Make a request and return a generator that gives back strings
  103. or JSON decoded lines of the body data, or raise an error if
  104. it contained an eror.
  105. """
  106. (response, isjson) = self._do_req(method, url, query, body,
  107. stream = True, headers = headers)
  108. for line in response.iter_lines():
  109. if isjson:
  110. yield json.loads(line)
  111. else:
  112. yield line
  113. def get_gen(self, url, params = None):
  114. """Simple GET (parameters in URL) returning a generator"""
  115. return self._req_gen("GET", url, params)
  116. # Not much use for a POST or PUT generator, since they don't
  117. # return much data.