summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--README.rst5
-rw-r--r--docs/specification.rst47
-rwxr-xr-xsynapse/app/homeserver.py4
-rw-r--r--synapse/config/_base.py3
-rw-r--r--synapse/federation/replication.py7
-rw-r--r--synapse/federation/transport.py5
-rw-r--r--synapse/handlers/directory.py5
-rw-r--r--synapse/handlers/login.py6
-rw-r--r--synapse/handlers/register.py13
-rw-r--r--synapse/http/client.py283
-rwxr-xr-xsynctl2
-rw-r--r--tests/federation/test_federation.py5
-rw-r--r--tests/handlers/test_directory.py5
13 files changed, 195 insertions, 195 deletions
diff --git a/README.rst b/README.rst
index 0459d54634..f40492b8a0 100644
--- a/README.rst
+++ b/README.rst
@@ -51,6 +51,7 @@ To get up and running:
 
 - To run your own **private** homeserver on localhost:8008, generate a basic
   config file: ``./synctl start`` will give you instructions on how to do this.
+  For this purpose, you can use 'localhost' or your hostname as a server name.
   Once you've done so, running ``./synctl start`` again will start your private
   home sserver. You will find a webclient running at http://localhost:8008.
   Please use a recent Chrome or Firefox for now (or Safari if you don't need
@@ -253,7 +254,7 @@ http://localhost:8080. Simply run::
 Running The Demo Web Client
 ===========================
 
-The homeserver runs a web client by default at http://localhost:8080.
+The homeserver runs a web client by default at https://localhost:8448/.
 
 If this is the first time you have used the client from that browser (it uses
 HTML5 local storage to remember its config), you will need to log in to your
@@ -273,7 +274,7 @@ account. Your name will take the form of::
 
 Specify your desired localpart in the topmost box of the "Register for an
 account" form, and click the "Register" button. Hostnames can contain ports if
-required due to lack of SRV records (e.g. @matthew:localhost:8080 on an
+required due to lack of SRV records (e.g. @matthew:localhost:8448 on an
 internal synapse sandbox running on localhost)
 
 
diff --git a/docs/specification.rst b/docs/specification.rst
index f169cf02ce..d07a667a5e 100644
--- a/docs/specification.rst
+++ b/docs/specification.rst
@@ -167,7 +167,7 @@ The following diagram shows an ``m.room.message`` event being sent in the room
        |   matrix.org     |<-------Federation------->|   domain.com     |
        +------------------+                          +------------------+
                 |       .................................        |
-                |______|     Partially Shared State      |_______|
+                |______|           Shared State          |_______|
                        | Room ID: !qporfwt:matrix.org    |
                        | Servers: matrix.org, domain.com |
                        | Members:                        |
@@ -177,11 +177,10 @@ The following diagram shows an ``m.room.message`` event being sent in the room
 
 Federation maintains shared state between multiple home servers, such that when
 an event is sent to a room, the home server knows where to forward the event on
-to, and how to process the event. Home servers do not need to have completely
-shared state in order to participate in a room. State is scoped to a single
-room, and federation ensures that all home servers have the information they
-need, even if that means the home server has to request more information from
-another home server before processing the event.
+to, and how to process the event. State is scoped to a single room, and
+federation ensures that all home servers have the information they need, even
+if that means the home server has to request more information from another home
+server before processing the event.
 
 Room Aliases
 ------------
@@ -191,7 +190,7 @@ Each room can also have multiple "Room Aliases", which looks like::
   #room_alias:domain
 
   .. TODO
-      - Need to specify precise grammar for Room IDs
+      - Need to specify precise grammar for Room Aliases
 
 A room alias "points" to a room ID and is the human-readable label by which
 rooms are publicised and discovered.  The room ID the alias is pointing to can
@@ -201,6 +200,9 @@ over time to point to a different room ID. For this reason, Clients SHOULD
 resolve the room alias to a room ID once and then use that ID on subsequent
 requests.
 
+When resolving a room alias the server will also respond with a list of servers
+that are in the room that can be used to join via.
+
 ::
 
           GET    
@@ -239,8 +241,8 @@ authentication of the 3PID.  Identity servers are also used to preserve the
 mapping indefinitely, by replicating the mappings across multiple ISes.
 
 Usage of an IS is not required in order for a client application to be part of
-the Matrix ecosystem. However, by not using an IS, discovery of users is
-greatly impacted.
+the Matrix ecosystem. However, without one clients will not be able to look up
+user IDs using 3PIDs.
 
 API Standards
 -------------
@@ -719,7 +721,7 @@ options which can be set when creating a room:
   Description:
     A ``public`` visibility indicates that the room will be shown in the public
     room list. A ``private`` visibility will hide the room from the public room
-    list. Rooms default to ``public`` visibility if this key is not included.
+    list. Rooms default to ``private`` visibility if this key is not included.
 
 ``room_alias_name``
   Type: 
@@ -772,7 +774,7 @@ Example::
 
   {
     "visibility": "public", 
-    "room_alias_name": "the pub",
+    "room_alias_name": "thepub",
     "name": "The Grand Duke Pub",
     "topic": "All about happy hour"
   }
@@ -790,7 +792,7 @@ includes:
  - ``m.room.send_event_level`` : The power level required in order to send a
    message in this room.
  - ``m.room.ops_level`` : The power level required in order to kick or ban a
-   user from the room.
+   user from the room or redact an event in the room.
 
 See `Room Events`_ for more information on these events.
 
@@ -847,6 +849,7 @@ Joining rooms
 -------------
 .. TODO-doc kegan
   - TODO: What does the home server have to do to join a user to a room?
+    See SPEC-30.
 
 Users need to join a room in order to send and receive events in that room. A
 user can join a room by making a request to |/join/<room_alias_or_id>|_ with::
@@ -945,11 +948,7 @@ directly by sending the following request to
 See the `Room events`_ section for more information on ``m.room.member``.
 
 Once a user has left a room, that room will no longer appear on the
-|initialSync|_ API. Be aware that leaving a room is not equivalent to have
-never been in that room. A user who has previously left a room still maintains
-some residual state in that room. Their membership state will be marked as
-``leave``. This contrasts with a user who has *never been invited or joined to
-that room* who will not have any membership state for that room. 
+|initialSync|_ API.
 
 If all members in a room leave, that room becomes eligible for deletion. 
 
@@ -1314,7 +1313,7 @@ prefixed with ``m.``
   Type: 
     State event
   JSON format:
-    ``{ "ban_level": <int>, "kick_level": <int> }``
+    ``{ "ban_level": <int>, "kick_level": <int>, "redact_level": <int> }``
   Example:
     ``{ "ban_level": 5, "kick_level": 5 }``
   Description:
@@ -1391,6 +1390,10 @@ prefixed with ``m.``
 
 m.room.message msgtypes
 -----------------------
+
+.. TODO-spec
+   How a client should handle unknown message types.
+
 Each ``m.room.message`` MUST have a ``msgtype`` key which identifies the type
 of message being sent. Each type has their own required and optional keys, as
 outlined below:
@@ -2044,9 +2047,7 @@ is another list containing the EDUs. This key may be entirely absent if there
 are no EDUs to transfer.
 
 (* Normally the PDU list will be non-empty, but the server should cope with
-receiving an "empty" transaction, as this is useful for informing peers of
-other transaction IDs they should be aware of. This effectively acts as a push
-mechanism to encourage peers to continue to replicate content.)
+receiving an "empty" transaction.)
 
 PDUs and EDUs
 -------------
@@ -2129,7 +2130,7 @@ For state updates:
   Description:
     The asserted power level of the user performing the update.
     
-``min_update``
+``required_power_level``
   Type:
     Integer
   Description:
@@ -2147,7 +2148,7 @@ For state updates:
   Description:
     The PDU id of the update this replaces.
     
-``user``
+``user_id``
   Type:
     String
   Description:
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/config/_base.py b/synapse/config/_base.py
index 809f9c922b..b3aeff327c 100644
--- a/synapse/config/_base.py
+++ b/synapse/config/_base.py
@@ -123,7 +123,8 @@ class Config(object):
                 #   style mode markers into the file, to hint to people that
                 #   this is a YAML file.
                 yaml.dump(config, config_file, default_flow_style=False)
-            print "A config file has been generated in %s (your server name is '%s'). Please review this file and customise it to your needs." % (config_args.config_path, config['server_name'])
+            print "A config file has been generated in %s for server name '%s') with corresponding SSL keys and self-signed certificates. Please review this file and customise it to your needs." % (config_args.config_path, config['server_name'])
+            print "If this server name is incorrect, you will need to regenerate the SSL certificates"
             sys.exit(0)
 
         return cls(args)
diff --git a/synapse/federation/replication.py b/synapse/federation/replication.py
index 96b82f00cb..5f96f79998 100644
--- a/synapse/federation/replication.py
+++ b/synapse/federation/replication.py
@@ -159,7 +159,8 @@ class ReplicationLayer(object):
         return defer.succeed(None)
 
     @log_function
-    def make_query(self, destination, query_type, args):
+    def make_query(self, destination, query_type, args,
+                   retry_on_dns_fail=True):
         """Sends a federation Query to a remote homeserver of the given type
         and arguments.
 
@@ -174,7 +175,9 @@ class ReplicationLayer(object):
             a Deferred which will eventually yield a JSON object from the
             response
         """
-        return self.transport_layer.make_query(destination, query_type, args)
+        return self.transport_layer.make_query(
+            destination, query_type, args, retry_on_dns_fail=retry_on_dns_fail
+        )
 
     @defer.inlineCallbacks
     @log_function
diff --git a/synapse/federation/transport.py b/synapse/federation/transport.py
index afc777ec9e..93296af204 100644
--- a/synapse/federation/transport.py
+++ b/synapse/federation/transport.py
@@ -193,13 +193,14 @@ class TransportLayer(object):
 
     @defer.inlineCallbacks
     @log_function
-    def make_query(self, destination, query_type, args):
+    def make_query(self, destination, query_type, args, retry_on_dns_fail):
         path = PREFIX + "/query/%s" % query_type
 
         response = yield self.client.get_json(
             destination=destination,
             path=path,
-            args=args
+            args=args,
+            retry_on_dns_fail=retry_on_dns_fail,
         )
 
         defer.returnValue(response)
diff --git a/synapse/handlers/directory.py b/synapse/handlers/directory.py
index 84c3a1d56f..a56830d520 100644
--- a/synapse/handlers/directory.py
+++ b/synapse/handlers/directory.py
@@ -18,7 +18,6 @@ from twisted.internet import defer
 from ._base import BaseHandler
 
 from synapse.api.errors import SynapseError
-from synapse.http.client import HttpClient
 from synapse.api.events.room import RoomAliasesEvent
 
 import logging
@@ -98,8 +97,8 @@ class DirectoryHandler(BaseHandler):
                 query_type="directory",
                 args={
                     "room_alias": room_alias.to_string(),
-                    HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
-                }
+                },
+                retry_on_dns_fail=False,
             )
 
             if result and "room_id" in result and "servers" in result:
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..df562aa762 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:
@@ -173,7 +176,7 @@ class RegistrationHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def _bind_threepid(self, creds, mxid):
-        httpCli = PlainHttpClient(self.hs)
+        httpCli = IdentityServerHttpClient(self.hs)
         data = yield httpCli.post_urlencoded_get_json(
             creds['idServer'],
             "/_matrix/identity/api/v1/3pid/bind",
@@ -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..5c2fbd1f87 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,12 +59,8 @@ class MatrixHttpAgent(_AgentBase):
                                          parsed_URI.originForm)
 
 
-class TwistedHttpClient(HttpClient):
-    """ Wrapper around the twisted HTTP client api.
-
-    Attributes:
-        agent (twisted.web.client.Agent): The twisted Agent used to send the
-            requests.
+class BaseHttpClient(object):
+    """Base class for HTTP clients using twisted.
     """
 
     def __init__(self, hs):
@@ -115,101 +68,6 @@ class TwistedHttpClient(HttpClient):
         self.hs = hs
 
     @defer.inlineCallbacks
-    def put_json(self, destination, path, data, on_send_callback=None):
-        if destination in _destination_mappings:
-            destination = _destination_mappings[destination]
-
-        response = yield self._create_request(
-            destination.encode("ascii"),
-            "PUT",
-            path.encode("ascii"),
-            producer=_JsonProducer(data),
-            headers_dict={"Content-Type": ["application/json"]},
-            on_send_callback=on_send_callback,
-        )
-
-        logger.debug("Getting resp body")
-        body = yield readBody(response)
-        logger.debug("Got resp body")
-
-        defer.returnValue((response.code, body))
-
-    @defer.inlineCallbacks
-    def get_json(self, destination, path, args={}):
-        if destination in _destination_mappings:
-            destination = _destination_mappings[destination]
-
-        logger.debug("get_json args: %s", args)
-
-        retry_on_dns_fail = True
-        if HttpClient.RETRY_DNS_LOOKUP_FAILURES in args:
-            # FIXME: This isn't ideal, but the interface exposed in get_json
-            # isn't comprehensive enough to give caller's any control over
-            # their connection mechanics.
-            retry_on_dns_fail = args.pop(HttpClient.RETRY_DNS_LOOKUP_FAILURES)
-
-        query_bytes = urllib.urlencode(args, True)
-        logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
-
-        response = yield self._create_request(
-            destination.encode("ascii"),
-            "GET",
-            path.encode("ascii"),
-            query_bytes=query_bytes,
-            retry_on_dns_fail=retry_on_dns_fail
-        )
-
-        body = yield readBody(response)
-
-        defer.returnValue(json.loads(body))
-
-    @defer.inlineCallbacks
-    def post_urlencoded_get_json(self, destination, path, args={}):
-        if destination in _destination_mappings:
-            destination = _destination_mappings[destination]
-
-        logger.debug("post_urlencoded_get_json args: %s", args)
-        query_bytes = urllib.urlencode(args, True)
-
-        response = yield self._create_request(
-            destination.encode("ascii"),
-            "POST",
-            path.encode("ascii"),
-            producer=FileBodyProducer(StringIO(urllib.urlencode(args))),
-            headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]}
-        )
-
-        body = yield readBody(response)
-
-        defer.returnValue(json.loads(body))
-        
-    # XXX FIXME : I'm so sorry.
-    @defer.inlineCallbacks
-    def post_urlencoded_get_raw(self, destination, path, accept_partial=False, args={}):
-        if destination in _destination_mappings:
-            destination = _destination_mappings[destination]
-
-        query_bytes = urllib.urlencode(args, True)
-
-        response = yield self._create_request(
-            destination.encode("ascii"),
-            "POST",
-            path.encode("ascii"),
-            producer=FileBodyProducer(StringIO(urllib.urlencode(args))),
-            headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]}
-        )
-
-        try:
-            body = yield readBody(response)
-            defer.returnValue(body)
-        except PartialDownloadError as e:
-            if accept_partial:
-                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):
@@ -232,7 +90,6 @@ class TwistedHttpClient(HttpClient):
 
         retries_left = 5
 
-        # TODO: setup and pass in an ssl_context to enable TLS
         endpoint = self._getEndpoint(reactor, destination);
 
         while True:
@@ -283,6 +140,85 @@ class TwistedHttpClient(HttpClient):
 
         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.
+    """
+
+    RETRY_DNS_LOOKUP_FAILURES = "__retry_dns"
+
+    @defer.inlineCallbacks
+    def put_json(self, destination, path, data, on_send_callback=None):
+        """ 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",
+            path.encode("ascii"),
+            producer=_JsonProducer(data),
+            headers_dict={"Content-Type": ["application/json"]},
+            on_send_callback=on_send_callback,
+        )
+
+        logger.debug("Getting resp body")
+        body = yield readBody(response)
+        logger.debug("Got resp body")
+
+        defer.returnValue((response.code, body))
+
+    @defer.inlineCallbacks
+    def get_json(self, destination, path, args={}, retry_on_dns_fail=True):
+        """ 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)
+
+        query_bytes = urllib.urlencode(args, True)
+        logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
+
+        response = yield self._create_request(
+            destination.encode("ascii"),
+            "GET",
+            path.encode("ascii"),
+            query_bytes=query_bytes,
+            retry_on_dns_fail=retry_on_dns_fail
+        )
+
+        body = yield readBody(response)
+
+        defer.returnValue(json.loads(body))
+
+
     def _getEndpoint(self, reactor, destination):
         return matrix_endpoint(
             reactor, destination, timeout=10,
@@ -290,10 +226,63 @@ class TwistedHttpClient(HttpClient):
         )
 
 
-class PlainHttpClient(TwistedHttpClient):
+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={}):
+        logger.debug("post_urlencoded_get_json args: %s", args)
+        query_bytes = urllib.urlencode(args, True)
+
+        response = yield self._create_request(
+            destination.encode("ascii"),
+            "POST",
+            path.encode("ascii"),
+            producer=FileBodyProducer(StringIO(query_bytes)),
+            headers_dict={
+                "Content-Type": ["application/x-www-form-urlencoded"]
+            }
+        )
+
+        body = yield readBody(response)
+
+        defer.returnValue(json.loads(body))
+
+
+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={}):
+        query_bytes = urllib.urlencode(args, True)
+
+        response = yield self._create_request(
+            destination.encode("ascii"),
+            "POST",
+            path.encode("ascii"),
+            producer=FileBodyProducer(StringIO(query_bytes)),
+            headers_dict={
+                "Content-Type": ["application/x-www-form-urlencoded"]
+            }
+        )
+
+        try:
+            body = yield readBody(response)
+            defer.returnValue(body)
+        except PartialDownloadError as e:
+            if accept_partial:
+                defer.returnValue(e.response)
+            else:
+                raise e
 
 def _print_ex(e):
     if hasattr(e, "reasons") and e.reasons:
diff --git a/synctl b/synctl
index 7523fd3dbc..c227a9e1e4 100755
--- a/synctl
+++ b/synctl
@@ -14,7 +14,7 @@ case "$1" in
   start)
     if [ ! -f "$CONFIGFILE" ]; then
       echo "No config file found"
-      echo "To generate a config file, run '$SYNAPSE -c $CONFIGFILE --generate-config'"
+      echo "To generate a config file, run '$SYNAPSE -c $CONFIGFILE --generate-config --server-name=<server name>'"
       exit 1
     fi
 
diff --git a/tests/federation/test_federation.py b/tests/federation/test_federation.py
index bb17e9aafe..d95b9013a3 100644
--- a/tests/federation/test_federation.py
+++ b/tests/federation/test_federation.py
@@ -253,7 +253,7 @@ class FederationTestCase(unittest.TestCase):
         response = yield self.federation.make_query(
             destination="remote",
             query_type="a-question",
-            args={"one": "1", "two": "2"}
+            args={"one": "1", "two": "2"},
         )
 
         self.assertEquals({"your": "response"}, response)
@@ -261,7 +261,8 @@ class FederationTestCase(unittest.TestCase):
         self.mock_http_client.get_json.assert_called_with(
             destination="remote",
             path="/_matrix/federation/v1/query/a-question",
-            args={"one": "1", "two": "2"}
+            args={"one": "1", "two": "2"},
+            retry_on_dns_fail=True,
         )
 
     @defer.inlineCallbacks
diff --git a/tests/handlers/test_directory.py b/tests/handlers/test_directory.py
index dd5d85dde6..e10a49a8ac 100644
--- a/tests/handlers/test_directory.py
+++ b/tests/handlers/test_directory.py
@@ -20,7 +20,6 @@ from twisted.internet import defer
 from mock import Mock
 
 from synapse.server import HomeServer
-from synapse.http.client import HttpClient
 from synapse.handlers.directory import DirectoryHandler
 from synapse.storage.directory import RoomAliasMapping
 
@@ -95,8 +94,8 @@ class DirectoryTestCase(unittest.TestCase):
             query_type="directory",
             args={
                 "room_alias": "#another:remote",
-                HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
-            }
+            },
+            retry_on_dns_fail=False,
         )
 
     @defer.inlineCallbacks