diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py index 82afb04c7d..1acc87e99c 100644 --- a/synapse/app/homeserver.py +++ b/synapse/app/homeserver.py @@ -21,8 +21,12 @@ from synapse.server import HomeServer from twisted.internet import reactor from twisted.enterprise import adbapi from twisted.python.log import PythonLoggingObserver -from synapse.http.server import TwistedHttpServer +from twisted.web.resource import Resource +from twisted.web.static import File +from twisted.web.server import Site +from synapse.http.server import TwistedHttpServer, JsonResource from synapse.http.client import TwistedHttpClient +from synapse.rest.base import CLIENT_PREFIX from daemonize import Daemonize @@ -41,6 +45,15 @@ class SynapseHomeServer(HomeServer): def build_http_client(self): return TwistedHttpClient() + def build_resource_for_client(self): + return JsonResource() + + def build_resource_for_federation(self): + return JsonResource() + + def build_resource_for_web_client(self): + return File("webclient") + def build_db_pool(self): """ Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we don't have to worry about overwriting existing content. @@ -73,6 +86,65 @@ class SynapseHomeServer(HomeServer): return pool + def create_resource_tree(self): + """Create the resource tree for this Home Server. + + This in unduly complicated because Twisted does not support putting + child resources more than 1 level deep at a time. + """ + desired_tree = ( # list of tuples containing (path_str, Resource) + ("/matrix/client", self.get_resource_for_web_client()), + (CLIENT_PREFIX, self.get_resource_for_client()) + ) + + self.root_resource = Resource() + # ideally we'd just use getChild and putChild but getChild doesn't work + # unless you give it a Request object IN ADDITION to the name :/ So + # instead, we'll store a copy of this mapping so we can actually add + # extra resources to existing nodes. See self._resource_id for the key. + resource_mappings = {} + for (full_path, resource) in desired_tree: + logging.info("Attaching %s to path %s", resource, full_path) + last_resource = self.root_resource + for path_seg in full_path.split('/')[1:-1]: + if not path_seg in last_resource.listNames(): + # resource doesn't exist + child_resource = Resource() + last_resource.putChild(path_seg, child_resource) + res_id = self._resource_id(last_resource, path_seg) + resource_mappings[res_id] = child_resource + last_resource = child_resource + else: + # we have an existing Resource, pull it out. + res_id = self._resource_id(last_resource, path_seg) + last_resource = resource_mappings[res_id] + + # now attach the actual resource + last_path_seg = full_path.split('/')[-1] + last_resource.putChild(last_path_seg, resource) + res_id = self._resource_id(last_resource, last_path_seg) + resource_mappings[res_id] = resource + + return self.root_resource + + def _resource_id(self, resource, path_seg): + """Construct an arbitrary resource ID so you can retrieve the mapping + later. + + If you want to represent resource A putChild resource B with path C, + the mapping should looks like _resource_id(A,C) = B. + + Args: + resource (Resource): The *parent* Resource + path_seg (str): The name of the child Resource to be attached. + Returns: + str: A unique string which can be a key to the child Resource. + """ + return "%s-%s" % (resource, path_seg) + + def start_listening(self, port): + reactor.listenTCP(port, Site(self.root_resource)) + def setup_logging(verbosity=0, filename=None, config_path=None): """ Sets up logging with verbosity levels. @@ -150,7 +222,8 @@ def setup(): hs.register_servlets() - hs.get_http_server().start_listening(args.port) + hs.create_resource_tree() + hs.start_listening(args.port) hs.build_db_pool() diff --git a/synapse/http/server.py b/synapse/http/server.py index d7f4b691bc..32dfd836cd 100644 --- a/synapse/http/server.py +++ b/synapse/http/server.py @@ -185,3 +185,8 @@ def respond_with_json_bytes(request, code, json_bytes, send_cors=False): request.write(json_bytes) request.finish() return NOT_DONE_YET + + +# FIXME: Temp, just so the new name can be used without breaking the world. +class JsonResource(TwistedHttpServer): + pass \ No newline at end of file diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py index 74a372e2ff..82c09f4c4a 100644 --- a/synapse/rest/__init__.py +++ b/synapse/rest/__init__.py @@ -32,19 +32,15 @@ class RestServletFactory(object): """ def __init__(self, hs): - http_server = hs.get_http_server() + client_resource = hs.get_resource_for_client() # TODO(erikj): There *must* be a better way of doing this. - room.register_servlets(hs, http_server) - events.register_servlets(hs, http_server) - register.register_servlets(hs, http_server) - login.register_servlets(hs, http_server) - profile.register_servlets(hs, http_server) - public.register_servlets(hs, http_server) - presence.register_servlets(hs, http_server) - im.register_servlets(hs, http_server) - directory.register_servlets(hs, http_server) - - def register_web_client(self, hs): - http_server = hs.get_http_server() - webclient.register_servlets(hs, http_server) + room.register_servlets(hs, client_resource) + events.register_servlets(hs, client_resource) + register.register_servlets(hs, client_resource) + login.register_servlets(hs, client_resource) + profile.register_servlets(hs, client_resource) + public.register_servlets(hs, client_resource) + presence.register_servlets(hs, client_resource) + im.register_servlets(hs, client_resource) + directory.register_servlets(hs, client_resource) diff --git a/synapse/rest/base.py b/synapse/rest/base.py index 65d417f757..124b3a2e0c 100644 --- a/synapse/rest/base.py +++ b/synapse/rest/base.py @@ -16,6 +16,8 @@ """ This module contains base REST classes for constructing REST servlets. """ import re +CLIENT_PREFIX = "/matrix/client/api/v1" + def client_path_pattern(path_regex): """Creates a regex compiled client path with the correct client path @@ -27,7 +29,7 @@ def client_path_pattern(path_regex): Returns: SRE_Pattern """ - return re.compile("^/matrix/client/api/v1" + path_regex) + return re.compile("^" + CLIENT_PREFIX + path_regex) class RestServlet(object): diff --git a/synapse/server.py b/synapse/server.py index 96830a88b1..537e431375 100644 --- a/synapse/server.py +++ b/synapse/server.py @@ -70,6 +70,9 @@ class BaseHomeServer(object): 'room_lock_manager', 'notifier', 'distributor', + 'resource_for_client', + 'resource_for_federation', + 'resource_for_web_client', ] def __init__(self, hostname, **kwargs): @@ -178,9 +181,6 @@ class HomeServer(BaseHomeServer): def register_servlets(self): """ Register all servlets associated with this HomeServer. - - Args: - host_web_client (bool): True to host the web client as well. """ # Simply building the ServletFactory is sufficient to have it register - factory = self.get_rest_servlet_factory() + self.get_rest_servlet_factory() diff --git a/tests/rest/test_presence.py b/tests/rest/test_presence.py index f013abbee4..98b308dcdb 100644 --- a/tests/rest/test_presence.py +++ b/tests/rest/test_presence.py @@ -51,6 +51,7 @@ class PresenceStateTestCase(unittest.TestCase): hs = HomeServer("test", db_pool=None, http_client=None, + resource_for_client=self.mock_server, http_server=self.mock_server, ) @@ -108,6 +109,7 @@ class PresenceListTestCase(unittest.TestCase): hs = HomeServer("test", db_pool=None, http_client=None, + resource_for_client=self.mock_server, http_server=self.mock_server, ) @@ -184,6 +186,7 @@ class PresenceEventStreamTestCase(unittest.TestCase): db_pool=None, http_client=None, http_server=self.mock_server, + resource_for_client=self.mock_server, datastore=Mock(spec=[ "set_presence_state", "get_presence_list", diff --git a/tests/rest/test_profile.py b/tests/rest/test_profile.py index 46e6137775..975d32f64d 100644 --- a/tests/rest/test_profile.py +++ b/tests/rest/test_profile.py @@ -44,6 +44,7 @@ class ProfileTestCase(unittest.TestCase): db_pool=None, http_client=None, http_server=self.mock_server, + resource_for_client=self.mock_server, federation=Mock(), replication_layer=Mock(), )