import re from utils import NOT_FOUND, add_text_headers, HTTP_METHODS import db from backend import * class Route: """ Представляет из себя класс роута. При инициализации принимает допустимые методы и регулярное выражение. Содержит методы для работы роутинга. Чтобы созданный путь стал обрабатываться необходимо поместить объект Route в таблицу роутера. """ def __init__(self, callback, url_format, methods=None): self.methods = ['GET'] if methods is None else methods self.url_pattern = re.compile(url_format) self.callback = callback def check_route(self, url, method) -> bool: """ По указанным URL и методу проверят подходит ли URL под соответсвующие настройки пути. """ match = self.url_pattern.fullmatch(url) return bool(match) and len(match.groups()) == self.url_pattern.groups and method in self.methods def invoke_callback(self, url, method, query): """ Как и `check_route` проверяет параметр на валидность, но если он валиден, то вызывается соответствующий обработчик. """ match = self.url_pattern.fullmatch(url) groups = match.groups() matched = match and len(groups) == self.url_pattern.groups and method in self.methods if not matched: return NOT_FOUND, NOT_FOUND return self.callback(query, *match.groups()) # url_format - регулярное выражение def route(url_format, methods=None): """ *Используется как декоратор.* Указание при каком url и методах в запросе должна вызываться декорируемая функция. При инициализации декоратора надо указать url регулярное выражение и http методы. В декорируемую функцию передаются параметры query из тела запроса и сматченные группы из регулярного выражения `url_format`. Добавляет декорируемые функции в дерево роутера. """ if methods is None: methods = ['GET'] def wrapper(func): def inner(url, query, *args, **kwargs): pattern = re.compile(url_format) match = re.fullmatch(pattern, url) if match is None or len(match.groups()) != pattern.groups: return NOT_FOUND, '404 NOT FOUND' return func(query, *match.groups(), *args, **kwargs) # Добавляем вызываемую функцию в дерево роутера. # Благодаря этому указывать паттерн url и метод нужно указывать # только в инициализаторе декоратора, а функция run сама разберется # при каких условиях нужно вызвать конкретную функцию _router_table.append(Route(func, url_format, methods)) return inner return wrapper def run(request): """ Принимает разобранный http-запрос, добавляет новые cookie-данные в базу данных. Ищет в таблице роутера функцию, которую необходимо вызвать для обработки текущего запроса. При нахождении такой функции передает ей все необходимые данные о запросе. Если подходящая функция не была найдена возвращает обработчику статус `404 Not Found`.\ """ res = NOT_FOUND, NOT_FOUND for key, value in request['cookies'].items(): db.set_cookie(key, value) method, url = request['method'], request['url'] for route in _router_table: if route.check_route(url, method): res = route.invoke_callback(url, method, request['query']) break return add_text_headers(*res) _router_table = [ # Пока ничего лучше не придумал Route(fallback_wrong_method, '/.*', list(set(HTTP_METHODS) ^ {'GET', 'POST'})), Route(index_get, '/'), Route(divide_get, r'/div/(\d+)/to/(\d+)/?'), Route(divide_post, r'/div/?', ['POST']), Route(show_errors_get, r'/show_errors/(\d{1})/?'), Route(show_errors_post, r'/show_errors/?', ['POST']), Route(set_cookie_get, r'/set_cookie/=/(.*)/?'), Route(set_cookie_post, r'/set_cookie/=/?', ['POST']), Route(short_log_get, r'/short_log/(\d{1})/?'), Route(short_log_post, r'/short_log/?', ['POST']), ]