123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- # 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
|