changeset 979:1d558dfb32ca

server: pages redirection: when using a redirection dict, a new "page" key can be used to redirect to a named page. "args" can be added to specified named arguments to set (will be put in request.args, in addition to existing ones). The redirection is done dynamically, during the request workflow.
author Goffi <goffi@goffi.org>
date Sun, 12 Nov 2017 12:56:46 +0100
parents c8cafe316f6f
children bcacf970f970
files src/server/server.py
diffstat 1 files changed, 84 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/src/server/server.py	Sun Nov 12 12:51:56 2017 +0100
+++ b/src/server/server.py	Sun Nov 12 12:56:46 2017 +0100
@@ -117,25 +117,31 @@
         ## redirections
         self.redirections = {}
         if options['url_redirections_dict'] and not options['url_redirections_profile']:
+            # FIXME: url_redirections_profile should not be needed. It is currently used to
+            #        redirect to an URL which associate the profile with the service, but this
+            #        is not clean, and service should be explicitly specified
             raise ValueError(u"url_redirections_profile need to be filled if you want to use url_redirections_dict")
 
         for old, new_data in options['url_redirections_dict'].iteritems():
             # new_data can be a dictionary or a unicode url
             if isinstance(new_data, dict):
-                # new_data dict must contain either "url" or "path" key (exclusive)
+                # new_data dict must contain either "url", "page" or "path" key (exclusive)
                 # if "path" is used, a file url is constructed with it
-                try:
+                if len({'path', 'url', 'page'}.intersection(new_data.keys())) != 1:
+                    raise ValueError(u'You must have one and only one of "url", "page" or "path" key in your url_redirections_dict data')
+                if 'url' in new_data:
                     new = new_data['url']
-                except KeyError:
-                    try:
-                        path = new_data['path']
-                    except KeyError:
-                        raise ValueError(u'if you use a dict for url_redirections data, it must contain the "url" or a "file" key')
-                    else:
-                        new = 'file:{}'.format(urllib.quote(path))
-                else:
-                    if 'path' in new_data:
-                        raise ValueError(u'You can\'t have "url" and "path" keys at the same time in url_redirections')
+                elif 'page' in new_data:
+                    new = new_data
+                    new['type'] = 'page'
+                    if 'args' in new:
+                        # we need lists in args because it will be used
+                        # as it in request.args
+                        for k,v in new['args'].iteritems():
+                            if isinstance(v, basestring):
+                                new['args'][k] = [v]
+                elif 'path' in new_data:
+                    new = 'file:{}'.format(urllib.quote(new_data['path']))
             else:
                 new = new_data
                 new_data = {}
@@ -148,6 +154,17 @@
                 raise ValueError(u"redirected url must start with '/', got {}".format(old))
             else:
                 old = self._normalizeURL(old)
+
+            if isinstance(new, dict):
+                # dict are handled differently, they contain data
+                # which are use dynamically when the request is done
+                self.redirections[old] = new
+                if not old:
+                    if new['type'] == 'page':
+                        log.info(_(u"Root URL redirected to page {name}").format(name=new['page']))
+                continue
+
+            # at this point we have a rediction URL in new, we can parse it
             new_url = urlparse.urlsplit(new.encode('utf-8'))
 
             # we handle the known URL schemes
@@ -200,7 +217,7 @@
 
             self.redirections[old] = request_data
             if not old:
-                log.info(u"Root URL redirected to {uri}".format(uri=request_data[1].decode('utf-8')))
+                log.info(_(u"Root URL redirected to {uri}").format(uri=request_data[1].decode('utf-8')))
 
         # no need to keep url_redirections*, they will not be used anymore
         del options['url_redirections_dict']
@@ -255,28 +272,55 @@
         @param request_data(tuple): data returned by self._getRequestData
         @return (web_resource.Resource): resource to use
         """
-        path_list, uri, path, args = request_data
+        # recursion check
         try:
             request._redirected
         except AttributeError:
             pass
         else:
+            try:
+                dummy, uri, dummy, dummy = request_data
+            except ValueError:
+                uri = u''
             log.warning(D_(u"recursive redirection, please fix this URL:\n{old} ==> {new}").format(
                 old=request.uri.decode('utf-8'),
                 new=uri.decode('utf-8'),
                 ))
             return web_resource.NoResource()
-        log.debug(u"Redirecting URL {old} to {new}".format(
-            old=request.uri.decode('utf-8'),
-            new=uri.decode('utf-8'),
-            ))
-        # we change the request to reflect the new url
+
         request._redirected = True # here to avoid recursive redirections
-        request.postpath = path_list[1:]
-        request.uri = uri
-        request.path = path
-        request.args = args
-        # and we start again to look for a child with the new url
+
+        if isinstance(request_data, dict):
+            if request_data['type'] == 'page':
+                try:
+                    page = LiberviaPage.getPageByName(request_data['page'])
+                except KeyError:
+                    log.error(_(u"Can't find page named \"{name}\" requested in redirection").format(
+                        name = request_data['page']))
+                    return web_resource.NoResource()
+                try:
+                    request.args.update(request_data['args'])
+                except KeyError:
+                    pass
+                except Exception:
+                    log.error(_(u"Invalid args in redirection: {args}").format(request_data['args']))
+                    return web_resource.NoResource()
+                return page
+            else:
+                raise exceptions.InternalError(u'unknown request_data type')
+        else:
+            path_list, uri, path, args = request_data
+            log.debug(u"Redirecting URL {old} to {new}".format(
+                old=request.uri.decode('utf-8'),
+                new=uri.decode('utf-8'),
+                ))
+            # we change the request to reflect the new url
+            request.postpath = path_list[1:]
+            request.uri = uri
+            request.path = path
+            request.args = args
+
+        # we start again to look for a child with the new url
         return self.getChildWithDefault(path_list[0], request)
 
     def getChildWithDefault(self, name, request):
@@ -292,14 +336,19 @@
         if isinstance(resource, web_resource.NoResource):
             # if nothing was found, we try our luck with redirections
             # XXX: we want redirections to happen only if everything else failed
-            current_url = '/'.join([name] + request.postpath).lower()
-            try:
-                request_data = self.redirections[current_url]
-            except KeyError:
-                # no redirection for this url
-                pass
-            else:
-                return self._redirect(request, request_data)
+
+            # first we check with current URL
+            # there our last try with the remaining URL
+            for path_elts in (request.prepath,
+                         [name] + request.postpath):
+                current_url = '/'.join(path_elts).lower()
+                try:
+                    request_data = self.redirections[current_url]
+                except KeyError:
+                    # no redirection for this url
+                    pass
+                else:
+                    return self._redirect(request, request_data)
 
         return resource
 
@@ -1475,14 +1524,15 @@
             url = os.path.join(u'/', u'/'.join(callback_data['pre_path']), callback_data['callback'](self, uri_data))
         return url
 
-    def getPageByName(self, name):
+    @classmethod
+    def getPageByName(cls, name):
         """retrieve page instance from its name
 
         @param name(unicode): name of the page
         @return (LiberviaPage): page instance
         @raise KeyError: the page doesn't exist
         """
-        return self.named_pages[name]
+        return cls.named_pages[name]
 
     def getPageRedirectURL(self, request, page_name=u'login', url=None):
         """generate URL for a page with redirect_url parameter set