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