Source code for link.wsgi.router

# -*- coding: utf-8 -*-

from b3j0f.conf import Configurable, category, Parameter
from b3j0f.utils.path import lookup

from link.wsgi import CONF_BASE_PATH

from inspect import isclass
from re import match
import traceback
import logging


@Configurable(
    paths='{0}/router.conf'.format(CONF_BASE_PATH),
    conf=category(
        'ROUTER',
        Parameter(name='urlpatterns', ptype=dict),
        Parameter(name='middlewares', ptype=list)
    )
)
[docs]class Router(object): """ Request dispatcher. Contains URL patterns as dict: - a regex to match the URL as key - a dict associated HTTP methods to Python callable objects Also contains list of middlewares (Python classes) to apply. Example of configuration: .. code-block:: javascript { "ROUTER": { "urlpatterns": { "^/hello$": { "GET": "python.path.to.hello_function" } }, "middlewares": [ "python.path.to.MiddlewareClass" ] } } """ @property def urlpatterns(self): if not hasattr(self, '_urlpatterns'): self.urlpatterns = None return self._urlpatterns @urlpatterns.setter def urlpatterns(self, value): if value is None: value = {} self._urlpatterns = value @property def middlewares(self): if not hasattr(self, '_middlewares'): self.middlewares = None return self._middlewares @middlewares.setter def middlewares(self, value): if value is None: value = [] self._middlewares = value def __init__(self, urlpatterns=None, middlewares=None, *args, **kwargs): """ :param urlpatterns: URL patterns config (used to override config file) :type urlpatterns: dict :param middlewares: Middlewares config (used to override config file) :type middlewares: list """ super(Router, self).__init__(*args, **kwargs) self.logger = logging.getLogger('link.wsgi.router') if urlpatterns is not None: self.urlpatterns = urlpatterns if middlewares is not None: self.middlewares = middlewares self.objcache = {}
[docs] def dispatch(self, req, resp): """ Dispatch request to handler, which will fill response. :param req: request object :type req: link.wsgi.req.Request :param resp: response object :type resp: link.wsgi.resp.Response """ for urlpattern in self.urlpatterns: # check URL for every configured patterns if match(urlpattern, req.path) is not None: self.logger.debug('URLPattern({0})'.format(urlpattern)) route = self.urlpatterns[urlpattern] # check if request method is allowed for route if req.method in route: try: h = hash('{0}{1}'.format(urlpattern, req.method)) # check if handler has already been instantiated if h not in self.objcache: # fetch handler, instantiate it if it's a class handler = lookup(route[req.method]) if isclass(handler): # the handler for a class is a method # the method's name is the request method handler = getattr( handler(), req.method.lower() ) self.objcache[h] = handler else: handler = self.objcache[h] # instantiate middlewares if needed middlewares = [] for middleware in self.middlewares: h = hash(middleware) if h not in self.objcache: self.objcache[h] = lookup(middleware)() middlewares.append(self.objcache[h]) # apply middlewares on request/response/handler abort = False for middleware in middlewares: if middleware.before(req, resp, handler): abort = True break # if a middleware returned True, the request is aborted if abort: self.logger.debug('Request aborted') break # pass request/response to the handler handler(req, resp) # apply middlewares on request/response/handler again for middleware in reversed(middlewares): if middleware.after(req, resp, handler): break except Exception: # if any error happens, return a 500 internal error resp.status = 500 resp.content = traceback.format_exc() else: # if the method was not configured for the route, return a # 405 method not allowed resp.status = 405 resp.content = 'Unkown method: {0}'.format(req.method) # here a handler was found, successfully executed or not # we do not need to check for another urlpattern break else: # no urlpattern matched, return a 404 not found resp.status = 404 resp.content = 'No route matching URL: {0}'.format(req.path)