From 4f11518934c3e032a763f115a73261414d67f87b Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Thu, 2 Oct 2014 13:57:48 +0100 Subject: Split PlainHttpClient into separate clients for talking to Identity servers and talking to Capatcha servers --- synapse/app/homeserver.py | 4 +- synapse/handlers/directory.py | 4 +- synapse/handlers/login.py | 6 +- synapse/handlers/register.py | 11 +- synapse/http/client.py | 290 ++++++++++++++++++++------------------- tests/handlers/test_directory.py | 4 +- 6 files changed, 163 insertions(+), 156 deletions(-) diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py index 2f1b954902..61d574a00f 100755 --- a/synapse/app/homeserver.py +++ b/synapse/app/homeserver.py @@ -25,7 +25,7 @@ from twisted.web.static import File from twisted.web.server import Site from synapse.http.server import JsonResource, RootRedirect from synapse.http.content_repository import ContentRepoResource -from synapse.http.client import TwistedHttpClient +from synapse.http.client import MatrixHttpClient from synapse.api.urls import ( CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX ) @@ -47,7 +47,7 @@ logger = logging.getLogger(__name__) class SynapseHomeServer(HomeServer): def build_http_client(self): - return TwistedHttpClient(self) + return MatrixHttpClient(self) def build_resource_for_client(self): return JsonResource() diff --git a/synapse/handlers/directory.py b/synapse/handlers/directory.py index 84c3a1d56f..cec7737e09 100644 --- a/synapse/handlers/directory.py +++ b/synapse/handlers/directory.py @@ -18,7 +18,7 @@ from twisted.internet import defer from ._base import BaseHandler from synapse.api.errors import SynapseError -from synapse.http.client import HttpClient +from synapse.http.client import MatrixHttpClient from synapse.api.events.room import RoomAliasesEvent import logging @@ -98,7 +98,7 @@ class DirectoryHandler(BaseHandler): query_type="directory", args={ "room_alias": room_alias.to_string(), - HttpClient.RETRY_DNS_LOOKUP_FAILURES: False + MatrixHttpClient.RETRY_DNS_LOOKUP_FAILURES: False } ) diff --git a/synapse/handlers/login.py b/synapse/handlers/login.py index 80ffdd2726..3f152e18f0 100644 --- a/synapse/handlers/login.py +++ b/synapse/handlers/login.py @@ -17,7 +17,7 @@ from twisted.internet import defer from ._base import BaseHandler from synapse.api.errors import LoginError, Codes -from synapse.http.client import PlainHttpClient +from synapse.http.client import IdentityServerHttpClient from synapse.util.emailutils import EmailException import synapse.util.emailutils as emailutils @@ -97,10 +97,10 @@ class LoginHandler(BaseHandler): @defer.inlineCallbacks def _query_email(self, email): - httpCli = PlainHttpClient(self.hs) + httpCli = IdentityServerHttpClient(self.hs) data = yield httpCli.get_json( 'matrix.org:8090', # TODO FIXME This should be configurable. "/_matrix/identity/api/v1/lookup?medium=email&address=" + "%s" % urllib.quote(email) ) - defer.returnValue(data) \ No newline at end of file + defer.returnValue(data) diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py index a019d770d4..266495056e 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -22,7 +22,8 @@ from synapse.api.errors import ( ) from ._base import BaseHandler import synapse.util.stringutils as stringutils -from synapse.http.client import PlainHttpClient +from synapse.http.client import IdentityServerHttpClient +from synapse.http.client import CaptchaServerHttpClient import base64 import bcrypt @@ -154,7 +155,9 @@ class RegistrationHandler(BaseHandler): @defer.inlineCallbacks def _threepid_from_creds(self, creds): - httpCli = PlainHttpClient(self.hs) + # TODO: get this from the homeserver rather than creating a new one for + # each request + httpCli = IdentityServerHttpClient(self.hs) # XXX: make this configurable! trustedIdServers = ['matrix.org:8090'] if not creds['idServer'] in trustedIdServers: @@ -203,7 +206,9 @@ class RegistrationHandler(BaseHandler): @defer.inlineCallbacks def _submit_captcha(self, ip_addr, private_key, challenge, response): - client = PlainHttpClient(self.hs) + # TODO: get this from the homeserver rather than creating a new one for + # each request + client = CaptchaServerHttpClient(self.hs) data = yield client.post_urlencoded_get_raw( "www.google.com:80", "/recaptcha/api/verify", diff --git a/synapse/http/client.py b/synapse/http/client.py index 822afeec1d..e02cce5642 100644 --- a/synapse/http/client.py +++ b/synapse/http/client.py @@ -36,49 +36,6 @@ import urllib logger = logging.getLogger(__name__) -class HttpClient(object): - """ Interface for talking json over http - """ - RETRY_DNS_LOOKUP_FAILURES = "__retry_dns" - - def put_json(self, destination, path, data): - """ Sends the specifed json data using PUT - - Args: - destination (str): The remote server to send the HTTP request - to. - path (str): The HTTP path. - 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. On a 4xx or 5xx error response a - CodeMessageException is raised. - """ - pass - - def get_json(self, destination, path, args=None): - """ Get's some json from the given host homeserver and path - - Args: - destination (str): The remote server to send the HTTP request - to. - path (str): The HTTP path. - 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 *any* HTTP response. - - The result of the deferred is a tuple of `(code, response)`, - where `response` is a dict representing the decoded JSON body. - """ - pass - - class MatrixHttpAgent(_AgentBase): def __init__(self, reactor, pool=None): @@ -102,23 +59,114 @@ class MatrixHttpAgent(_AgentBase): parsed_URI.originForm) -class TwistedHttpClient(HttpClient): - """ Wrapper around the twisted HTTP client api. +class BaseHttpClient(object): + """Base class for HTTP clients using twisted. + """ + + def __init__(self, hs): + self.agent = MatrixHttpAgent(reactor) + self.hs = hs + + @defer.inlineCallbacks + def _create_request(self, destination, method, path_bytes, param_bytes=b"", + query_bytes=b"", producer=None, headers_dict={}, + retry_on_dns_fail=True, on_send_callback=None): + """ Creates and sends a request to the given url + """ + headers_dict[b"User-Agent"] = [b"Synapse"] + headers_dict[b"Host"] = [destination] + + logger.debug("Sending request to %s: %s %s;%s?%s", + destination, method, path_bytes, param_bytes, query_bytes) + + logger.debug( + "Types: %s", + [ + type(destination), type(method), type(path_bytes), + type(param_bytes), + type(query_bytes) + ] + ) + + retries_left = 5 + + endpoint = self._getEndpoint(reactor, destination); + + while True: + if on_send_callback: + on_send_callback(destination, method, path_bytes, producer) + + try: + response = yield self.agent.request( + destination, + endpoint, + method, + path_bytes, + param_bytes, + query_bytes, + Headers(headers_dict), + producer + ) + + logger.debug("Got response to %s", method) + break + except Exception as e: + if not retry_on_dns_fail and isinstance(e, DNSLookupError): + logger.warn("DNS Lookup failed to %s with %s", destination, + e) + raise SynapseError(400, "Domain specified not found.") + + logger.exception("Got error in _create_request") + _print_ex(e) + + if retries_left: + yield sleep(2 ** (5 - retries_left)) + retries_left -= 1 + else: + raise + + if 200 <= response.code < 300: + # We need to update the transactions table to say it was sent? + pass + else: + # :'( + # Update transactions table? + logger.error( + "Got response %d %s", response.code, response.phrase + ) + raise CodeMessageException( + response.code, response.phrase + ) + + defer.returnValue(response) + + +class MatrixHttpClient(BaseHttpClient): + """ Wrapper around the twisted HTTP client api. Implements Attributes: agent (twisted.web.client.Agent): The twisted Agent used to send the requests. """ - def __init__(self, hs): - self.agent = MatrixHttpAgent(reactor) - self.hs = hs + RETRY_DNS_LOOKUP_FAILURES = "__retry_dns" @defer.inlineCallbacks def put_json(self, destination, path, data, on_send_callback=None): - if destination in _destination_mappings: - destination = _destination_mappings[destination] + """ Sends the specifed json data using PUT + Args: + destination (str): The remote server to send the HTTP request + to. + path (str): The HTTP path. + 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. On a 4xx or 5xx error response a + CodeMessageException is raised. + """ response = yield self._create_request( destination.encode("ascii"), "PUT", @@ -136,9 +184,23 @@ class TwistedHttpClient(HttpClient): @defer.inlineCallbacks def get_json(self, destination, path, args={}): - if destination in _destination_mappings: - destination = _destination_mappings[destination] + """ Get's some json from the given host homeserver and path + + Args: + destination (str): The remote server to send the HTTP request + to. + path (str): The HTTP path. + 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 *any* HTTP response. + The result of the deferred is a tuple of `(code, response)`, + where `response` is a dict representing the decoded JSON body. + """ logger.debug("get_json args: %s", args) retry_on_dns_fail = True @@ -163,6 +225,22 @@ class TwistedHttpClient(HttpClient): defer.returnValue(json.loads(body)) + + def _getEndpoint(self, reactor, destination): + return matrix_endpoint( + reactor, destination, timeout=10, + ssl_context_factory=self.hs.tls_context_factory + ) + + +class IdentityServerHttpClient(BaseHttpClient): + """Separate HTTP client for talking to the Identity servers since they + don't use SRV records and talk x-www-form-urlencoded rather than JSON. + """ + def _getEndpoint(self, reactor, destination): + #TODO: This should be talking TLS + return matrix_endpoint(reactor, destination, timeout=10) + @defer.inlineCallbacks def post_urlencoded_get_json(self, destination, path, args={}): if destination in _destination_mappings: @@ -176,16 +254,25 @@ class TwistedHttpClient(HttpClient): "POST", path.encode("ascii"), producer=FileBodyProducer(StringIO(urllib.urlencode(args))), - headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]} + headers_dict={ + "Content-Type": ["application/x-www-form-urlencoded"] + } ) body = yield readBody(response) defer.returnValue(json.loads(body)) - - # XXX FIXME : I'm so sorry. + + +class CaptchaServerHttpClient(MatrixHttpClient): + """Separate HTTP client for talking to google's captcha servers""" + + def _getEndpoint(self, reactor, destination): + return matrix_endpoint(reactor, destination, timeout=10) + @defer.inlineCallbacks - def post_urlencoded_get_raw(self, destination, path, accept_partial=False, args={}): + def post_urlencoded_get_raw(self, destination, path, accept_partial=False, + args={}): if destination in _destination_mappings: destination = _destination_mappings[destination] @@ -196,7 +283,9 @@ class TwistedHttpClient(HttpClient): "POST", path.encode("ascii"), producer=FileBodyProducer(StringIO(urllib.urlencode(args))), - headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]} + headers_dict={ + "Content-Type": ["application/x-www-form-urlencoded"] + } ) try: @@ -207,93 +296,6 @@ class TwistedHttpClient(HttpClient): defer.returnValue(e.response) else: raise e - - - @defer.inlineCallbacks - def _create_request(self, destination, method, path_bytes, param_bytes=b"", - query_bytes=b"", producer=None, headers_dict={}, - retry_on_dns_fail=True, on_send_callback=None): - """ Creates and sends a request to the given url - """ - headers_dict[b"User-Agent"] = [b"Synapse"] - headers_dict[b"Host"] = [destination] - - logger.debug("Sending request to %s: %s %s;%s?%s", - destination, method, path_bytes, param_bytes, query_bytes) - - logger.debug( - "Types: %s", - [ - type(destination), type(method), type(path_bytes), - type(param_bytes), - type(query_bytes) - ] - ) - - retries_left = 5 - - # TODO: setup and pass in an ssl_context to enable TLS - endpoint = self._getEndpoint(reactor, destination); - - while True: - if on_send_callback: - on_send_callback(destination, method, path_bytes, producer) - - try: - response = yield self.agent.request( - destination, - endpoint, - method, - path_bytes, - param_bytes, - query_bytes, - Headers(headers_dict), - producer - ) - - logger.debug("Got response to %s", method) - break - except Exception as e: - if not retry_on_dns_fail and isinstance(e, DNSLookupError): - logger.warn("DNS Lookup failed to %s with %s", destination, - e) - raise SynapseError(400, "Domain specified not found.") - - logger.exception("Got error in _create_request") - _print_ex(e) - - if retries_left: - yield sleep(2 ** (5 - retries_left)) - retries_left -= 1 - else: - raise - - if 200 <= response.code < 300: - # We need to update the transactions table to say it was sent? - pass - else: - # :'( - # Update transactions table? - logger.error( - "Got response %d %s", response.code, response.phrase - ) - raise CodeMessageException( - response.code, response.phrase - ) - - defer.returnValue(response) - - def _getEndpoint(self, reactor, destination): - return matrix_endpoint( - reactor, destination, timeout=10, - ssl_context_factory=self.hs.tls_context_factory - ) - - -class PlainHttpClient(TwistedHttpClient): - def _getEndpoint(self, reactor, destination): - return matrix_endpoint(reactor, destination, timeout=10) - def _print_ex(e): if hasattr(e, "reasons") and e.reasons: diff --git a/tests/handlers/test_directory.py b/tests/handlers/test_directory.py index dd5d85dde6..0c31502dd4 100644 --- a/tests/handlers/test_directory.py +++ b/tests/handlers/test_directory.py @@ -20,7 +20,7 @@ from twisted.internet import defer from mock import Mock from synapse.server import HomeServer -from synapse.http.client import HttpClient +from synapse.http.client import MatrixHttpClient from synapse.handlers.directory import DirectoryHandler from synapse.storage.directory import RoomAliasMapping @@ -95,7 +95,7 @@ class DirectoryTestCase(unittest.TestCase): query_type="directory", args={ "room_alias": "#another:remote", - HttpClient.RETRY_DNS_LOOKUP_FAILURES: False + MatrixHttpClient.RETRY_DNS_LOOKUP_FAILURES: False } ) -- cgit 1.4.1