200 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			200 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
| # -*- coding: utf-8 -*-
 | |
| # 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 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={}):
 | |
|         """Wrapper of _create_request to issue a PUT request"""
 | |
| 
 | |
|         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={}):
 | |
|         """Wrapper of _create_request to issue a GET request"""
 | |
|         return self._create_request("GET", url, headers_dict=headers_dict)
 | |
| 
 | |
|     @defer.inlineCallbacks
 | |
|     def do_request(
 | |
|         self, method, url, data=None, qparams=None, jsonreq=True, headers={}
 | |
|     ):
 | |
|         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={}):
 | |
|         """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))
 | |
|         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
 |