Skip to content
Snippets Groups Projects
http.py 6.33 KiB
Newer Older
  • Learn to ignore specific revisions
  • matrix.org's avatar
    matrix.org committed
    # -*- coding: utf-8 -*-
    
    Matthew Hodgson's avatar
    Matthew Hodgson committed
    # 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.
    
    
    from __future__ import print_function
    
    matrix.org's avatar
    matrix.org committed
    
    import json
    import urllib
    
    from pprint import pformat
    
    from twisted.internet import defer, reactor
    from twisted.web.client import Agent, readBody
    from twisted.web.http_headers import Headers
    
    matrix.org's avatar
    matrix.org committed
    
    
    class HttpClient(object):
        """ 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.
    
    matrix.org's avatar
    matrix.org committed
            """
            pass
    
        def get_json(self, url, args=None):
    
            """ Gets some json from the given host homeserver and path
    
    matrix.org's avatar
    matrix.org committed
    
            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.
    
    matrix.org's avatar
    matrix.org committed
            """
            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(
    
    Amber Brown's avatar
    Amber Brown committed
                url, data, headers_dict={"Content-Type": ["application/json"]}
    
    matrix.org's avatar
    matrix.org committed
            )
            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={}):
            """ Wrapper of _create_request to issue a PUT request
            """
    
            if "Content-Type" not in headers_dict:
    
    Amber Brown's avatar
    Amber Brown committed
                raise defer.error(RuntimeError("Must include Content-Type header for PUTs"))
    
    matrix.org's avatar
    matrix.org committed
    
            return self._create_request(
    
    Amber Brown's avatar
    Amber Brown committed
                "PUT", url, producer=_JsonProducer(json_data), headers_dict=headers_dict
    
    matrix.org's avatar
    matrix.org committed
            )
    
        def _create_get_request(self, url, headers_dict={}):
            """ Wrapper of _create_request to issue a GET request
            """
    
    Amber Brown's avatar
    Amber Brown committed
            return self._create_request("GET", url, headers_dict=headers_dict)
    
    matrix.org's avatar
    matrix.org committed
    
        @defer.inlineCallbacks
    
    Amber Brown's avatar
    Amber Brown committed
        def do_request(
            self, method, url, data=None, qparams=None, jsonreq=True, headers={}
        ):
    
    matrix.org's avatar
    matrix.org committed
            if qparams:
                url = "%s?%s" % (url, urllib.urlencode(qparams, True))
    
            if jsonreq:
                prod = _JsonProducer(data)
    
    Amber Brown's avatar
    Amber Brown committed
                headers["Content-Type"] = ["application/json"]
    
    matrix.org's avatar
    matrix.org committed
            else:
                prod = _RawProducer(data)
    
            if method in ["POST", "PUT"]:
    
    Amber Brown's avatar
    Amber Brown committed
                response = yield self._create_request(
                    method, url, producer=prod, headers_dict=headers
                )
    
    matrix.org's avatar
    matrix.org committed
            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={}):
            """ Creates and sends a request to the given url
            """
            headers_dict["User-Agent"] = ["Synapse Cmd Client"]
    
            retries_left = 5
    
            print("%s to %s with headers %s" % (method, url, headers_dict))
    
    matrix.org's avatar
    matrix.org committed
            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))
    
    matrix.org's avatar
    matrix.org committed
                    producer.data["password"] = temp
                else:
    
                    print(json.dumps(producer.data, indent=4))
    
    matrix.org's avatar
    matrix.org committed
    
            while True:
                try:
                    response = yield self.agent.request(
    
    Amber Brown's avatar
    Amber Brown committed
                        method, url.encode("UTF8"), Headers(headers_dict), producer
    
    matrix.org's avatar
    matrix.org committed
                    )
                    break
                except Exception as e:
    
                    print("uh oh: %s" % e)
    
    matrix.org's avatar
    matrix.org committed
                    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())))
    
    matrix.org's avatar
    matrix.org committed
            defer.returnValue(response)
    
        def sleep(self, seconds):
            d = defer.Deferred()
            reactor.callLater(seconds, d.callback, seconds)
            return d
    
    
    Amber Brown's avatar
    Amber Brown committed
    
    
    matrix.org's avatar
    matrix.org committed
    class _RawProducer(object):
        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
    
    
    Amber Brown's avatar
    Amber Brown committed
    
    
    matrix.org's avatar
    matrix.org committed
    class _JsonProducer(object):
        """ Used by the twisted http client to create the HTTP body from json
        """
    
    Amber Brown's avatar
    Amber Brown committed
    
    
    matrix.org's avatar
    matrix.org committed
        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):