|
- # Copyright 2014-2016 OpenMarket Ltd
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
-
- import json
- import urllib
- from pprint import pformat
- from typing import Optional
-
- from twisted.internet import defer, reactor
- from twisted.web.client import Agent, readBody
- from twisted.web.http_headers import Headers
-
-
- class HttpClient:
- """Interface for talking json over http"""
-
- def put_json(self, url, data):
- """Sends the specifed json data using PUT
-
- Args:
- url (str): The URL to PUT data to.
- data (dict): A dict containing the data that will be used as
- the request body. This will be encoded as JSON.
-
- Returns:
- Deferred: Succeeds when we get a 2xx HTTP response. The result
- will be the decoded JSON body.
- """
- pass
-
- def get_json(self, url, args=None):
- """Gets some json from the given host homeserver and path
-
- Args:
- url (str): The URL to GET data from.
- args (dict): A dictionary used to create query strings, defaults to
- None.
- **Note**: The value of each key is assumed to be an iterable
- and *not* a string.
-
- Returns:
- Deferred: Succeeds when we get a 2xx HTTP response. The result
- will be the decoded JSON body.
- """
- pass
-
-
- class TwistedHttpClient(HttpClient):
- """Wrapper around the twisted HTTP client api.
-
- Attributes:
- agent (twisted.web.client.Agent): The twisted Agent used to send the
- requests.
- """
-
- def __init__(self):
- self.agent = Agent(reactor)
-
- @defer.inlineCallbacks
- def put_json(self, url, data):
- response = yield self._create_put_request(
- url, data, headers_dict={"Content-Type": ["application/json"]}
- )
- body = yield readBody(response)
- defer.returnValue((response.code, body))
-
- @defer.inlineCallbacks
- def get_json(self, url, args=None):
- if args:
- # generates a list of strings of form "k=v".
- qs = urllib.urlencode(args, True)
- url = "%s?%s" % (url, qs)
- response = yield self._create_get_request(url)
- body = yield readBody(response)
- defer.returnValue(json.loads(body))
-
- def _create_put_request(self, url, json_data, headers_dict: Optional[dict] = None):
- """Wrapper of _create_request to issue a PUT request"""
- headers_dict = headers_dict or {}
-
- if "Content-Type" not in headers_dict:
- raise defer.error(RuntimeError("Must include Content-Type header for PUTs"))
-
- return self._create_request(
- "PUT", url, producer=_JsonProducer(json_data), headers_dict=headers_dict
- )
-
- def _create_get_request(self, url, headers_dict: Optional[dict] = None):
- """Wrapper of _create_request to issue a GET request"""
- return self._create_request("GET", url, headers_dict=headers_dict or {})
-
- @defer.inlineCallbacks
- def do_request(
- self,
- method,
- url,
- data=None,
- qparams=None,
- jsonreq=True,
- headers: Optional[dict] = None,
- ):
- headers = headers or {}
-
- if qparams:
- url = "%s?%s" % (url, urllib.urlencode(qparams, True))
-
- if jsonreq:
- prod = _JsonProducer(data)
- headers["Content-Type"] = ["application/json"]
- else:
- prod = _RawProducer(data)
-
- if method in ["POST", "PUT"]:
- response = yield self._create_request(
- method, url, producer=prod, headers_dict=headers
- )
- else:
- response = yield self._create_request(method, url)
-
- body = yield readBody(response)
- defer.returnValue(json.loads(body))
-
- @defer.inlineCallbacks
- def _create_request(
- self, method, url, producer=None, headers_dict: Optional[dict] = None
- ):
- """Creates and sends a request to the given url"""
- headers_dict = headers_dict or {}
-
- headers_dict["User-Agent"] = ["Synapse Cmd Client"]
-
- retries_left = 5
- print("%s to %s with headers %s" % (method, url, headers_dict))
- if self.verbose and producer:
- if "password" in producer.data:
- temp = producer.data["password"]
- producer.data["password"] = "[REDACTED]"
- print(json.dumps(producer.data, indent=4))
- producer.data["password"] = temp
- else:
- print(json.dumps(producer.data, indent=4))
-
- while True:
- try:
- response = yield self.agent.request(
- method, url.encode("UTF8"), Headers(headers_dict), producer
- )
- break
- except Exception as e:
- print("uh oh: %s" % e)
- if retries_left:
- yield self.sleep(2 ** (5 - retries_left))
- retries_left -= 1
- else:
- raise e
-
- if self.verbose:
- print("Status %s %s" % (response.code, response.phrase))
- print(pformat(list(response.headers.getAllRawHeaders())))
- defer.returnValue(response)
-
- def sleep(self, seconds):
- d = defer.Deferred()
- reactor.callLater(seconds, d.callback, seconds)
- return d
-
-
- class _RawProducer:
- def __init__(self, data):
- self.data = data
- self.body = data
- self.length = len(self.body)
-
- def startProducing(self, consumer):
- consumer.write(self.body)
- return defer.succeed(None)
-
- def pauseProducing(self):
- pass
-
- def stopProducing(self):
- pass
-
-
- class _JsonProducer:
- """Used by the twisted http client to create the HTTP body from json"""
-
- def __init__(self, jsn):
- self.data = jsn
- self.body = json.dumps(jsn).encode("utf8")
- self.length = len(self.body)
-
- def startProducing(self, consumer):
- consumer.write(self.body)
- return defer.succeed(None)
-
- def pauseProducing(self):
- pass
-
- def stopProducing(self):
- pass
|