Mercurial > libervia-web
diff libervia/server/server.py @ 1360:389a83eefe62
server: SàT applications integration:
- a SàT Application can be added to the menu (if necessary values are exposed), by using
the `sat-app:[application_name]` in `menu_json` or `menu_extra_json`. The application
will then be started with Libervia, and embedded, i.e. Libervia menu will appear and
application will be integrated under it.
- the same `sat-app:[application_name]` thing can be used in redirection, in this case the
redirection will reverse proxy directly the application, without embedding it (no
Libervia menu will appear)
- the ReverseProxy will replace headers if necessary to allow embedding in a iframe from
the same domain
- new `embed_app` page to embed a SàT Application
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 28 Sep 2020 21:12:21 +0200 |
parents | 2da573bf3f8b |
children | 626b7bbb7f90 |
line wrap: on
line diff
--- a/libervia/server/server.py Mon Sep 28 17:12:04 2020 +0200 +++ b/libervia/server/server.py Mon Sep 28 21:12:21 2020 +0200 @@ -31,6 +31,7 @@ from twisted.web import resource as web_resource from twisted.web import util as web_util from twisted.web import vhost +from . import proxy from twisted.python.components import registerAdapter from twisted.python import failure from twisted.python import filepath @@ -225,6 +226,8 @@ self.pages_redirects = {} self.cached_urls = {} self.main_menu = None + # map SàT application names => data + self.sat_apps = {} self.build_path = host.getBuildPath(site_name) self.build_path.mkdir(parents=True, exist_ok=True) self.dev_build_path = host.getBuildPath(site_name, dev=True) @@ -258,7 +261,66 @@ C.TEMPLATE_TPL_DIR, theme) - def _initRedirections(self, options): + def addResourceToPath(self, path: str, resource: web_resource.Resource) -> None: + """Add a resource to the given path + + A "NoResource" will be used for all intermediate segments + """ + segments, __, last_segment = path.rpartition("/") + url_segments = segments.split("/") if segments else [] + current = self + for segment in url_segments: + resource = web_resource.NoResource() + current.putChild(segment, resource) + current = resource + + current.putChild( + last_segment.encode('utf-8'), + resource + ) + + async def _startApp(self, app_name, extra=None): + if extra is None: + extra = {} + log.info(_( + "starting application {app_name}").format(app_name=app_name)) + await self.host.bridgeCall( + "applicationStart", app_name, data_format.serialise(extra) + ) + app_data = self.sat_apps[app_name] = data_format.deserialise( + await self.host.bridgeCall( + "applicationExposedGet", app_name, "", "")) + + try: + web_port = int(app_data['ports']['web'].split(':')[1]) + except (KeyError, ValueError): + log.warning(_( + "no web port found for application {app_name!r}, can't use it " + ).format(app_name=app_name)) + raise exceptions.DataError("no web port found") + + try: + url_prefix = app_data['url_prefix'].strip().rstrip('/') + except (KeyError, AttributeError) as e: + log.warning(_( + "no URL prefix specified for this application, we can't embed it: {msg}") + .format(msg=e)) + raise exceptions.DataError("no URL prefix") + + if not url_prefix.startswith('/'): + raise exceptions.DataError( + f"invalid URL prefix, it must start with '/': {url_prefix!r}") + + res = proxy.SatReverseProxyResource( + "localhost", + web_port, + url_prefix.encode() + ) + self.addResourceToPath(url_prefix, res) + + return app_data + + async def _initRedirections(self, options): url_redirections = options["url_redirections_dict"] url_redirections = url_redirections.get(self.site_name, {}) @@ -395,26 +457,40 @@ "file redirection must have an absolute path: e.g. " "file:/path/to/my/file") # for file redirection, we directly put child here - segments, __, last_segment = old.rpartition("/") - url_segments = segments.split("/") if segments else [] - current = self - for segment in url_segments: - resource = web_resource.NoResource() - current.putChild(segment, resource) - current = resource resource_class = ( ProtectedFile if new_data.get("protected", True) else static.File ) - current.putChild( - last_segment.encode('utf-8'), - resource_class(path, defaultType="application/octet-stream") - ) + res = resource_class(path, defaultType="application/octet-stream") + self.addResourceToPath(old, res) log.info("[{host_name}] Added redirection from /{old} to file system " "path {path}".format(host_name=self.host_name, old=old, path=path)) - continue # we don't want to use redirection system, so we continue here + + # we don't want to use redirection system, so we continue here + continue + + elif new_url.scheme == "sat-app": + # a SàT application + app_name = urllib.parse.unquote(new_url.path).lower().strip() + extra = {"url_prefix": f"/{old}"} + try: + await self._startApp(app_name, extra) + except Exception as e: + log.warning(_( + "Can't launch {app_name!r} for path /{old}: {e}").format( + app_name=app_name, old=old, e=e)) + continue + + log.info("[{host_name}] Added redirection from /{old} to application " + "{app_name}".format( + host_name=self.host_name, + old=old, + app_name=app_name)) + + # normal redirection system is not used here + continue else: raise NotImplementedError( "{scheme}: scheme is not managed for url_redirections_dict".format( @@ -432,7 +508,7 @@ if not "" in self.redirections: self.redirections[""] = self._getRequestData(C.LIBERVIA_PAGE_START) - def _setMenu(self, menus): + async def _setMenu(self, menus): menus = menus.get(self.site_name, []) main_menu = [] for menu in menus: @@ -448,6 +524,29 @@ log.error(msg) raise ValueError(msg) page_name, url = menu + elif menu.startswith("sat-app:"): + app_name = menu[8:].strip().lower() + app_data = await self._startApp(app_name) + front_url = app_data['front_url'] + options = self.host.options + url_redirections = options["url_redirections_dict"].setdefault( + self.site_name, {}) + if front_url in url_redirections: + raise exceptions.ConflictError( + f"There is already a redirection from {front_url!r}, can't add " + f"{app_name!r}") + + url_redirections[front_url] = { + "page": 'embed_app', + "path_args": [app_name] + } + + page_name = app_data.get('web_label', app_name).title() + url = front_url + + log.debug( + f"Application {app_name} added to menu of {self.site_name}" + ) else: page_name = menu try: @@ -861,7 +960,7 @@ await tasks_manager.parseTasks() await tasks_manager.runTasks() # FIXME: handle _setMenu in a more generic way, taking care of external sites - self.sat_root._setMenu(self.options["menu_json"]) + await self.sat_root._setMenu(self.options["menu_json"]) self.vhost_root.default = default_root existing_vhosts = {b'': default_root} @@ -912,7 +1011,7 @@ tasks_manager = TasksManager(self, res) await tasks_manager.parseTasks() await tasks_manager.runTasks() - res._setMenu(self.options["menu_json"]) + await res._setMenu(self.options["menu_json"]) self.vhost_root.addHost(host_name.encode('utf-8'), res) @@ -965,7 +1064,7 @@ # redirections for root in self.roots: - root._initRedirections(self.options) + await root._initRedirections(self.options) # no need to keep url_redirections_dict, it will not be used anymore del self.options["url_redirections_dict"]