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.
 
 
 
 
 
 

203 lines
6.1 KiB

  1. # -*- coding: utf-8 -*-
  2. from twisted.web.client import Agent, readBody
  3. from twisted.web.http_headers import Headers
  4. from twisted.internet import defer, reactor
  5. from pprint import pformat
  6. import json
  7. import urllib
  8. class HttpClient(object):
  9. """ Interface for talking json over http
  10. """
  11. def put_json(self, url, data):
  12. """ Sends the specifed json data using PUT
  13. Args:
  14. url (str): The URL to PUT data to.
  15. data (dict): A dict containing the data that will be used as
  16. the request body. This will be encoded as JSON.
  17. Returns:
  18. Deferred: Succeeds when we get *any* HTTP response.
  19. The result of the deferred is a tuple of `(code, response)`,
  20. where `response` is a dict representing the decoded JSON body.
  21. """
  22. pass
  23. def get_json(self, url, args=None):
  24. """ Get's some json from the given host homeserver and path
  25. Args:
  26. url (str): The URL to GET data from.
  27. args (dict): A dictionary used to create query strings, defaults to
  28. None.
  29. **Note**: The value of each key is assumed to be an iterable
  30. and *not* a string.
  31. Returns:
  32. Deferred: Succeeds when we get *any* HTTP response.
  33. The result of the deferred is a tuple of `(code, response)`,
  34. where `response` is a dict representing the decoded JSON body.
  35. """
  36. pass
  37. class TwistedHttpClient(HttpClient):
  38. """ Wrapper around the twisted HTTP client api.
  39. Attributes:
  40. agent (twisted.web.client.Agent): The twisted Agent used to send the
  41. requests.
  42. """
  43. def __init__(self):
  44. self.agent = Agent(reactor)
  45. @defer.inlineCallbacks
  46. def put_json(self, url, data):
  47. response = yield self._create_put_request(
  48. url,
  49. data,
  50. headers_dict={"Content-Type": ["application/json"]}
  51. )
  52. body = yield readBody(response)
  53. defer.returnValue((response.code, body))
  54. @defer.inlineCallbacks
  55. def get_json(self, url, args=None):
  56. if args:
  57. # generates a list of strings of form "k=v".
  58. qs = urllib.urlencode(args, True)
  59. url = "%s?%s" % (url, qs)
  60. response = yield self._create_get_request(url)
  61. body = yield readBody(response)
  62. defer.returnValue(json.loads(body))
  63. def _create_put_request(self, url, json_data, headers_dict={}):
  64. """ Wrapper of _create_request to issue a PUT request
  65. """
  66. if "Content-Type" not in headers_dict:
  67. raise defer.error(
  68. RuntimeError("Must include Content-Type header for PUTs"))
  69. return self._create_request(
  70. "PUT",
  71. url,
  72. producer=_JsonProducer(json_data),
  73. headers_dict=headers_dict
  74. )
  75. def _create_get_request(self, url, headers_dict={}):
  76. """ Wrapper of _create_request to issue a GET request
  77. """
  78. return self._create_request(
  79. "GET",
  80. url,
  81. headers_dict=headers_dict
  82. )
  83. @defer.inlineCallbacks
  84. def do_request(self, method, url, data=None, qparams=None, jsonreq=True, headers={}):
  85. if qparams:
  86. url = "%s?%s" % (url, urllib.urlencode(qparams, True))
  87. if jsonreq:
  88. prod = _JsonProducer(data)
  89. headers['Content-Type'] = ["application/json"];
  90. else:
  91. prod = _RawProducer(data)
  92. if method in ["POST", "PUT"]:
  93. response = yield self._create_request(method, url,
  94. producer=prod,
  95. headers_dict=headers)
  96. else:
  97. response = yield self._create_request(method, url)
  98. body = yield readBody(response)
  99. defer.returnValue(json.loads(body))
  100. @defer.inlineCallbacks
  101. def _create_request(self, method, url, producer=None, headers_dict={}):
  102. """ Creates and sends a request to the given url
  103. """
  104. headers_dict["User-Agent"] = ["Synapse Cmd Client"]
  105. retries_left = 5
  106. print "%s to %s with headers %s" % (method, url, headers_dict)
  107. if self.verbose and producer:
  108. if "password" in producer.data:
  109. temp = producer.data["password"]
  110. producer.data["password"] = "[REDACTED]"
  111. print json.dumps(producer.data, indent=4)
  112. producer.data["password"] = temp
  113. else:
  114. print json.dumps(producer.data, indent=4)
  115. while True:
  116. try:
  117. response = yield self.agent.request(
  118. method,
  119. url.encode("UTF8"),
  120. Headers(headers_dict),
  121. producer
  122. )
  123. break
  124. except Exception as e:
  125. print "uh oh: %s" % e
  126. if retries_left:
  127. yield self.sleep(2 ** (5 - retries_left))
  128. retries_left -= 1
  129. else:
  130. raise e
  131. if self.verbose:
  132. print "Status %s %s" % (response.code, response.phrase)
  133. print pformat(list(response.headers.getAllRawHeaders()))
  134. defer.returnValue(response)
  135. def sleep(self, seconds):
  136. d = defer.Deferred()
  137. reactor.callLater(seconds, d.callback, seconds)
  138. return d
  139. class _RawProducer(object):
  140. def __init__(self, data):
  141. self.data = data
  142. self.body = data
  143. self.length = len(self.body)
  144. def startProducing(self, consumer):
  145. consumer.write(self.body)
  146. return defer.succeed(None)
  147. def pauseProducing(self):
  148. pass
  149. def stopProducing(self):
  150. pass
  151. class _JsonProducer(object):
  152. """ Used by the twisted http client to create the HTTP body from json
  153. """
  154. def __init__(self, jsn):
  155. self.data = jsn
  156. self.body = json.dumps(jsn).encode("utf8")
  157. self.length = len(self.body)
  158. def startProducing(self, consumer):
  159. consumer.write(self.body)
  160. return defer.succeed(None)
  161. def pauseProducing(self):
  162. pass
  163. def stopProducing(self):
  164. pass