summaryrefslogtreecommitdiff
path: root/day7/router.py
blob: 1e3d5af4ff22e86f3b06aab1074b00a817f8a9f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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']),
]