This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
mandaye/mandaye/dispatcher.py

226 lines
8.9 KiB
Python

import copy
import re
from urlparse import urlparse
from importlib import import_module
from mandaye import config
from mandaye.filters.default import MandayeFilter
from mandaye.http import HTTPRequest, HTTPResponse
from mandaye.log import logger
from mandaye.mappers import default
from mandaye.response import _500, _302
from mandaye.exceptions import ImproperlyConfigured
# TODO: add an external url mapping
def import_mapping(name):
if not name:
return dict()
if not config.mappers.has_key(name):
logger.error("mapper %s not found" % name)
return dict()
module = config.mappers[name]
try:
mapper = import_module(module)
except ImportError, e:
raise ImproperlyConfigured('Error importing mapping %s: "%s"' % (module, e))
return mapper
class Dispatcher(object):
""" The dispatcher is the main class of Mandaye
It allows you to launch the right filter on the reqest and the response
"""
def __init__(self, env, target_url, mapper_name=None):
""" env: wsgi environ
target_url: the full url of your destination
mepper: python module with the mapper
"""
self.env = env
self.target = urlparse(target_url)
self.env['target'] = self.target
self.mapper_name = mapper_name
self.mapper = import_mapping(mapper_name)
self.env['urls'] = self.mapper.urls
auth_type = self.env['mandaye.config']['auth_type']
path = config.authentifications[auth_type]
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
module = import_module(module)
Auth = getattr(module, attr)
self.auth = Auth(env, self.mapper)
self.env['mandaye.auth'] = self.auth
self.env['mandaye.mapper'] = self.mapper
mapping = []
mapping.extend(self.auth.get_default_mapping())
mapping.extend(default.mapping)
mapping.extend(self.mapper.mapping)
logger.debug('Dispatcher mapping : %r', mapping)
self.req_mapping = self._parse_mapping(mapping)
def __get_mappings_hooks(self, mapper, req_mapping):
""" fill the request mapping with the right hooks
return req_mapping
"""
if not mapper.has_key('method') or \
mapper['method'] == self.env['REQUEST_METHOD']:
if mapper.has_key('scripts'):
req_mapping["on_response"].append(
{
'filter': MandayeFilter.add_js_header,
'values': {'scripts': mapper['scripts']},
'content-types': ['text/html']
}
)
for hookname in req_mapping:
if mapper.has_key(hookname):
if isinstance(req_mapping[hookname], list):
for entry in mapper[hookname]:
if entry.has_key('auth'):
entry['filter'] = getattr(self.auth, entry['auth'])
req_mapping[hookname].append(entry)
else:
if isinstance(mapper[hookname], dict) and \
mapper[hookname].has_key('auth'):
mapper[hookname]['filter'] = getattr(self.auth, mapper[hookname]['auth'])
req_mapping[hookname] = mapper[hookname]
return req_mapping
def _parse_mapping(self, mapping):
""" parse the mapping on every request
"""
req_mapping = {
'on_request': [],
'on_response': [],
'response': None,
'target': None,
'redirect': None,
'decompress': config.auto_decompress
}
if not mapping:
return req_mapping
for entry in mapping:
if entry.has_key('path'):
if isinstance(entry['path'], str):
if re.match(entry['path'], self.env['PATH_INFO'],
re.IGNORECASE):
req_mapping = self.__get_mappings_hooks(entry, req_mapping)
else:
for path in entry['path']:
if re.match(path, self.env['PATH_INFO'],
re.IGNORECASE):
req_mapping = self.__get_mappings_hooks(entry, req_mapping)
else:
logger.warning('Config error: you need to specify paths in your mapping')
return req_mapping
def _call_hook(self, hook, *args):
if hook and hook.has_key('filter'):
values = hook.get('values')
if not values:
values = dict()
return hook['filter'](self.env, values, *args)
else:
logger.warning("%s hook failed (no filter option)" % self.env['PATH_INFO'])
return None
def _is_cond_respected(self, hook, request, response):
if hook.has_key('condition') and \
not hook['condition'](self.env, request, response):
return False
return True
def set_request_target(self, request):
""" Add target url on not into the request
"""
request.target = self.target.geturl() + self.env['RAW_URI']
if self.req_mapping['target']:
if "//" in self.req_mapping['target']:
request.target = self.req_mapping['target']
else:
request.target = self.target.geturl() + self.req_mapping['target']
elif self.req_mapping['response'] and \
self._is_cond_respected(self.req_mapping['response'],
request, None):
request.target = None
elif self.req_mapping['redirect']:
request.target = None
return request
def get_response(self, request):
""" Called if you have a response hook for this request
"""
if self.req_mapping['redirect']:
return _302(self.req_mapping['redirect'])
elif self.req_mapping['response']:
logger.debug("Loading response hook(s)")
hook = self.req_mapping['response']
logger.debug('Using hook: %r', hook)
response = self._call_hook(hook, request, None)
if not response:
return _500(self.env["PATH_INFO"], "The response hook failed")
else:
return _500(self.env["PATH_INFO"], "no response and no target")
return response
def mod_request(self, request):
""" Modify the request
request: MandayeRequest object with cookies and headers
Return the request object """
# Calling hook function
for hook in self.req_mapping['on_request']:
if not self._is_cond_respected(hook, request, None):
continue
response = self._call_hook(hook, request)
if response and isinstance(response, HTTPResponse):
return response
elif response and isinstance(response, HTTPRequest):
logger.warning('DEPRECATED hook %s: a request hook must return None or an HTTPResponse nothing else',
hook['filter'])
elif response:
logger.warning("%s on_request hook (%s) must return None or an HTTPResponse nothing else",
self.env['PATH_INFO'], hook['filter'])
return
def mod_response(self, request, response):
""" Modify the response. This will load on_response filters.
request: the Mandaye request
response: MandayeResponse object with cookies, headers and HTML
you can modify the cookies and the HTTP headers """
content_type = response.headers.getheader('content-type')
if content_type:
content_type = content_type.split(';')[0]
# Calling hook function
for hook in self.req_mapping['on_response']:
if not self._is_cond_respected(hook, request, response):
continue
if hook.has_key('content-types') and content_type:
if content_type not in hook['content-types']:
logger.debug("Don't load filter %s (content-type %s doesn't match)" % (hook['filter'], content_type))
continue
if self.req_mapping['decompress']:
response.decompress()
try:
new_response = self._call_hook(hook, request, response)
except Exception, e:
new_response = _500(self.env['PATH_INFO'],
"Hook %s failed with error %s" % (hook, e),
exception=e,
env=self.env)
if new_response:
response = new_response
else:
logger.warning("%s On_response hook %s failed (empty answer)",
self.env['PATH_INFO'], hook['filter'])
return response