comparison src/server/server.py @ 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
comparison
equal deleted inserted replaced
978:c8cafe316f6f 979:1d558dfb32ca
115 super(LiberviaRootResource, self).__init__(*args, **kwargs) 115 super(LiberviaRootResource, self).__init__(*args, **kwargs)
116 116
117 ## redirections 117 ## redirections
118 self.redirections = {} 118 self.redirections = {}
119 if options['url_redirections_dict'] and not options['url_redirections_profile']: 119 if options['url_redirections_dict'] and not options['url_redirections_profile']:
120 # FIXME: url_redirections_profile should not be needed. It is currently used to
121 # redirect to an URL which associate the profile with the service, but this
122 # is not clean, and service should be explicitly specified
120 raise ValueError(u"url_redirections_profile need to be filled if you want to use url_redirections_dict") 123 raise ValueError(u"url_redirections_profile need to be filled if you want to use url_redirections_dict")
121 124
122 for old, new_data in options['url_redirections_dict'].iteritems(): 125 for old, new_data in options['url_redirections_dict'].iteritems():
123 # new_data can be a dictionary or a unicode url 126 # new_data can be a dictionary or a unicode url
124 if isinstance(new_data, dict): 127 if isinstance(new_data, dict):
125 # new_data dict must contain either "url" or "path" key (exclusive) 128 # new_data dict must contain either "url", "page" or "path" key (exclusive)
126 # if "path" is used, a file url is constructed with it 129 # if "path" is used, a file url is constructed with it
127 try: 130 if len({'path', 'url', 'page'}.intersection(new_data.keys())) != 1:
131 raise ValueError(u'You must have one and only one of "url", "page" or "path" key in your url_redirections_dict data')
132 if 'url' in new_data:
128 new = new_data['url'] 133 new = new_data['url']
129 except KeyError: 134 elif 'page' in new_data:
130 try: 135 new = new_data
131 path = new_data['path'] 136 new['type'] = 'page'
132 except KeyError: 137 if 'args' in new:
133 raise ValueError(u'if you use a dict for url_redirections data, it must contain the "url" or a "file" key') 138 # we need lists in args because it will be used
134 else: 139 # as it in request.args
135 new = 'file:{}'.format(urllib.quote(path)) 140 for k,v in new['args'].iteritems():
136 else: 141 if isinstance(v, basestring):
137 if 'path' in new_data: 142 new['args'][k] = [v]
138 raise ValueError(u'You can\'t have "url" and "path" keys at the same time in url_redirections') 143 elif 'path' in new_data:
144 new = 'file:{}'.format(urllib.quote(new_data['path']))
139 else: 145 else:
140 new = new_data 146 new = new_data
141 new_data = {} 147 new_data = {}
142 148
143 # some normalization 149 # some normalization
146 old = '' 152 old = ''
147 elif not old.startswith('/'): 153 elif not old.startswith('/'):
148 raise ValueError(u"redirected url must start with '/', got {}".format(old)) 154 raise ValueError(u"redirected url must start with '/', got {}".format(old))
149 else: 155 else:
150 old = self._normalizeURL(old) 156 old = self._normalizeURL(old)
157
158 if isinstance(new, dict):
159 # dict are handled differently, they contain data
160 # which are use dynamically when the request is done
161 self.redirections[old] = new
162 if not old:
163 if new['type'] == 'page':
164 log.info(_(u"Root URL redirected to page {name}").format(name=new['page']))
165 continue
166
167 # at this point we have a rediction URL in new, we can parse it
151 new_url = urlparse.urlsplit(new.encode('utf-8')) 168 new_url = urlparse.urlsplit(new.encode('utf-8'))
152 169
153 # we handle the known URL schemes 170 # we handle the known URL schemes
154 if new_url.scheme == 'xmpp': 171 if new_url.scheme == 'xmpp':
155 # XMPP URI 172 # XMPP URI
198 else: 215 else:
199 raise NotImplementedError(u"{scheme}: scheme is not managed for url_redirections_dict".format(scheme=new_url.scheme)) 216 raise NotImplementedError(u"{scheme}: scheme is not managed for url_redirections_dict".format(scheme=new_url.scheme))
200 217
201 self.redirections[old] = request_data 218 self.redirections[old] = request_data
202 if not old: 219 if not old:
203 log.info(u"Root URL redirected to {uri}".format(uri=request_data[1].decode('utf-8'))) 220 log.info(_(u"Root URL redirected to {uri}").format(uri=request_data[1].decode('utf-8')))
204 221
205 # no need to keep url_redirections*, they will not be used anymore 222 # no need to keep url_redirections*, they will not be used anymore
206 del options['url_redirections_dict'] 223 del options['url_redirections_dict']
207 del options['url_redirections_profile'] 224 del options['url_redirections_profile']
208 225
253 this is *NOT* a HTTP redirection, but equivalent to URL rewritting 270 this is *NOT* a HTTP redirection, but equivalent to URL rewritting
254 @param request(web.http.request): original request 271 @param request(web.http.request): original request
255 @param request_data(tuple): data returned by self._getRequestData 272 @param request_data(tuple): data returned by self._getRequestData
256 @return (web_resource.Resource): resource to use 273 @return (web_resource.Resource): resource to use
257 """ 274 """
258 path_list, uri, path, args = request_data 275 # recursion check
259 try: 276 try:
260 request._redirected 277 request._redirected
261 except AttributeError: 278 except AttributeError:
262 pass 279 pass
263 else: 280 else:
281 try:
282 dummy, uri, dummy, dummy = request_data
283 except ValueError:
284 uri = u''
264 log.warning(D_(u"recursive redirection, please fix this URL:\n{old} ==> {new}").format( 285 log.warning(D_(u"recursive redirection, please fix this URL:\n{old} ==> {new}").format(
265 old=request.uri.decode('utf-8'), 286 old=request.uri.decode('utf-8'),
266 new=uri.decode('utf-8'), 287 new=uri.decode('utf-8'),
267 )) 288 ))
268 return web_resource.NoResource() 289 return web_resource.NoResource()
269 log.debug(u"Redirecting URL {old} to {new}".format( 290
270 old=request.uri.decode('utf-8'),
271 new=uri.decode('utf-8'),
272 ))
273 # we change the request to reflect the new url
274 request._redirected = True # here to avoid recursive redirections 291 request._redirected = True # here to avoid recursive redirections
275 request.postpath = path_list[1:] 292
276 request.uri = uri 293 if isinstance(request_data, dict):
277 request.path = path 294 if request_data['type'] == 'page':
278 request.args = args 295 try:
279 # and we start again to look for a child with the new url 296 page = LiberviaPage.getPageByName(request_data['page'])
297 except KeyError:
298 log.error(_(u"Can't find page named \"{name}\" requested in redirection").format(
299 name = request_data['page']))
300 return web_resource.NoResource()
301 try:
302 request.args.update(request_data['args'])
303 except KeyError:
304 pass
305 except Exception:
306 log.error(_(u"Invalid args in redirection: {args}").format(request_data['args']))
307 return web_resource.NoResource()
308 return page
309 else:
310 raise exceptions.InternalError(u'unknown request_data type')
311 else:
312 path_list, uri, path, args = request_data
313 log.debug(u"Redirecting URL {old} to {new}".format(
314 old=request.uri.decode('utf-8'),
315 new=uri.decode('utf-8'),
316 ))
317 # we change the request to reflect the new url
318 request.postpath = path_list[1:]
319 request.uri = uri
320 request.path = path
321 request.args = args
322
323 # we start again to look for a child with the new url
280 return self.getChildWithDefault(path_list[0], request) 324 return self.getChildWithDefault(path_list[0], request)
281 325
282 def getChildWithDefault(self, name, request): 326 def getChildWithDefault(self, name, request):
283 # XXX: this method is overriden only for root url 327 # XXX: this method is overriden only for root url
284 # which is the only ones who need to be handled before other children 328 # which is the only ones who need to be handled before other children
290 resource = super(LiberviaRootResource, self).getChild(name, request) 334 resource = super(LiberviaRootResource, self).getChild(name, request)
291 335
292 if isinstance(resource, web_resource.NoResource): 336 if isinstance(resource, web_resource.NoResource):
293 # if nothing was found, we try our luck with redirections 337 # if nothing was found, we try our luck with redirections
294 # XXX: we want redirections to happen only if everything else failed 338 # XXX: we want redirections to happen only if everything else failed
295 current_url = '/'.join([name] + request.postpath).lower() 339
296 try: 340 # first we check with current URL
297 request_data = self.redirections[current_url] 341 # there our last try with the remaining URL
298 except KeyError: 342 for path_elts in (request.prepath,
299 # no redirection for this url 343 [name] + request.postpath):
300 pass 344 current_url = '/'.join(path_elts).lower()
301 else: 345 try:
302 return self._redirect(request, request_data) 346 request_data = self.redirections[current_url]
347 except KeyError:
348 # no redirection for this url
349 pass
350 else:
351 return self._redirect(request, request_data)
303 352
304 return resource 353 return resource
305 354
306 def createSimilarFile(self, path): 355 def createSimilarFile(self, path):
307 # XXX: this method need to be overriden to avoid recreating a LiberviaRootResource 356 # XXX: this method need to be overriden to avoid recreating a LiberviaRootResource
1473 return 1522 return
1474 else: 1523 else:
1475 url = os.path.join(u'/', u'/'.join(callback_data['pre_path']), callback_data['callback'](self, uri_data)) 1524 url = os.path.join(u'/', u'/'.join(callback_data['pre_path']), callback_data['callback'](self, uri_data))
1476 return url 1525 return url
1477 1526
1478 def getPageByName(self, name): 1527 @classmethod
1528 def getPageByName(cls, name):
1479 """retrieve page instance from its name 1529 """retrieve page instance from its name
1480 1530
1481 @param name(unicode): name of the page 1531 @param name(unicode): name of the page
1482 @return (LiberviaPage): page instance 1532 @return (LiberviaPage): page instance
1483 @raise KeyError: the page doesn't exist 1533 @raise KeyError: the page doesn't exist
1484 """ 1534 """
1485 return self.named_pages[name] 1535 return cls.named_pages[name]
1486 1536
1487 def getPageRedirectURL(self, request, page_name=u'login', url=None): 1537 def getPageRedirectURL(self, request, page_name=u'login', url=None):
1488 """generate URL for a page with redirect_url parameter set 1538 """generate URL for a page with redirect_url parameter set
1489 1539
1490 mainly used for login page with redirection to current page 1540 mainly used for login page with redirection to current page