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']),
]
|