From 11979f1e220587198ba05fd3c1f88d4f81195fff Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 24 May 2019 18:42:16 +0400 Subject: =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D0=B0=D1=8F=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0,=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=20=D1=88=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=82=D0=BE=D1=80.=20=D0=9F=D0=B0=D1=80?= =?UTF-8?q?=D1=81=D0=B8=D0=BD=D0=B3=20HTTP=20=D0=B7=D0=B0=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B2=20=D1=81=D1=82=D0=B0=D0=BB=20=D0=B1?= =?UTF-8?q?=D0=BE=D0=BB=D0=B5=D0=B5=20=D1=81=D1=82=D1=80=D0=BE=D0=B3=D0=B8?= =?UTF-8?q?=D0=BC.=20=D0=A1=D0=B5=D1=80=D0=B2=D0=B5=D1=80=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D0=BD=D1=87=D0=B8=D0=B2=D0=B0=D0=B5=D1=82=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D1=83=20=D0=BF=D1=80=D0=B8=20=D0=BD?= =?UTF-8?q?=D0=B5=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=BD=D0=BE=D0=BC=20HTTP?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=B5.=20=D0=9D=D0=B0?= =?UTF-8?q?=D1=87=D0=B0=D1=82=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B0=D0=B4=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B5=D0=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- day7/README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++ day7/backend.py | 9 ++++++-- day7/http_handler.py | 8 +++---- day7/templater.py | 5 ++++- day7/templates/form.html | 47 +++++++++++++++++++++++++++++++++------ day7/templates/index.html | 4 ++-- day7/utils.py | 34 +++++++++++++++++++++++----- 7 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 day7/README.md diff --git a/day7/README.md b/day7/README.md new file mode 100644 index 0000000..82f5d5c --- /dev/null +++ b/day7/README.md @@ -0,0 +1,56 @@ +# BrandNewServer Модули + +## templater.py +### `render_template(template_name, **kwargs)` +Нам постоянно нужно присылать однотипные HTML-странички и для этого пригодится HTML-шаблонизатор, +который может по небольшому количеству изменяемых параметров вернуть готовую страницу. + +## http_handler.py +### `main(host, port)` +Запускает сервер по адресу `host:port`. Также тут +обрабатывается остановка сервера при нажатии `Ctrl+C` + +### `log_requests(func)` +*Используется как декоратор.* + +Логирует все запросы, приходящие в функцию `process_request`. + +### `get_request(connection)` +Принимаем запрос от клиента и тут же парсим заголовки. Если в теле +запроса приходят дополнительные данные, то они тут тоже обрабатываются. + +### `process_request(address, method, url, http_ver, headers, query)` +*Эта функция логируется с помощью функции* `log_func` + +Проверяется валидность url, парсятся Cookie-заголовки. Если url валиден, +то запрос отправляется на обработку в `backend.py`. + +### `handle_connection(connection, address)` +Как только к серверу присоединяется какой-то клиент, +то это соединение обрабатывается тут. С помощью `get_request` +принимаются заголовки и отправляются в `process_request`. Ответ +клиенту, полученный из бекэнда, отправляется тут. + +### `get_color()` +Специфичная для этой программы функция. Возвращает цвет фона, который +должен быть у каждой возвращаемой страницы. + +## utils.py +Константы для ответов в http запросах. + +Функции, необходимые для обработки любых http-запросов. +* `add_headers(status, html)`: добавляет заголовки к ответной html-странице +* `validate_url(url)`: проверка url на правильность +* `parse_cookies(cookies_line)`: парсинг cookies +* `parse_headers(request_line)`: парсинг заголовков +* `parse_query(query_line)`: парсинг параметров в теле заголовка +* `url_decoder(url_line)`: [декодирование url](https://en.wikipedia.org/wiki/Percent-encoding) + +## backend.py +### `route(url_format, methods)` +*Используется как декоратор.* + +Указание при каком url и методах в запросе должна вызываться декорируемая функция. +При инициализации декоратора надо указать url регулярное выражение и http методы. +В декорируемую функцию передаются параметры query из тела запроса и сматченные группы +из регулярного выражения `url_format`. diff --git a/day7/backend.py b/day7/backend.py index 9f9b118..99a62bd 100644 --- a/day7/backend.py +++ b/day7/backend.py @@ -1,7 +1,7 @@ import re from templater import render_template -from utils import parse_cookies, add_headers, SUCCESS, BAD_REQUEST, NOT_FOUND +from utils import parse_cookies, add_headers, SUCCESS, BAD_REQUEST, NOT_FOUND, HTTP_METHODS from config import TEXT_TEMPLATE_NAME import db @@ -34,7 +34,7 @@ def route(url_format, methods=None): def run(method, url: str, cookies: dict, query): - res = NOT_FOUND, NOT_FOUND + 'KAVO' + res = NOT_FOUND, NOT_FOUND for key, value in cookies.items(): db.set_cookie(key, value) @@ -45,6 +45,11 @@ def run(method, url: str, cookies: dict, query): return add_headers(*res) +@route('/.*', list(set(HTTP_METHODS) ^ {'GET', 'POST'})) +def fallback_wrong_method(query, *args): + return NOT_FOUND, 'This method is not allowed' + + @route('/') def index_get(query, *args): return SUCCESS, render_template('form', color=get_color()) diff --git a/day7/http_handler.py b/day7/http_handler.py index e5a551b..4712afe 100644 --- a/day7/http_handler.py +++ b/day7/http_handler.py @@ -8,7 +8,7 @@ from config import * import db -def log_func(func): +def log_requests(func): def wrapper(*args, **kwargs): address = kwargs['address'] if 'address' in kwargs else args[0] method = kwargs['method'] if 'method' in kwargs else args[1] @@ -26,7 +26,7 @@ def log_func(func): return wrapper -@log_func +@log_requests def process_request(address, method, url, http_ver, headers: dict, query): if validate_url(url): try: @@ -116,7 +116,7 @@ def main(host, port): connection.close() del connection - except KeyboardInterrupt: + except (KeyboardInterrupt, ConnectionError): print('Stopping server...') try: @@ -133,5 +133,5 @@ def main(host, port): if __name__ == '__main__': - HOST, PORT = ADDR = '0.0.0.0', 4002 + HOST, PORT = ADDR = '0.0.0.0', 4001 main(HOST, PORT) diff --git a/day7/templater.py b/day7/templater.py index 92fb87f..ac9416e 100644 --- a/day7/templater.py +++ b/day7/templater.py @@ -2,4 +2,7 @@ def render_template(template_name, **kwargs): with open(f'templates/{template_name}.html') as f: data = f.read() - return data.format(**kwargs) + for name, value in kwargs.items(): + data = data.replace(f'%%{name}%%', value) + + return data diff --git a/day7/templates/form.html b/day7/templates/form.html index e1edb96..bff5030 100644 --- a/day7/templates/form.html +++ b/day7/templates/form.html @@ -1,26 +1,59 @@ - - + + + + + + + +
Show errors
-
+
+
+
Разделить на
-
+
+
+
Изменить Cookies
-
+
+
+
Short log
-
+
- +
+ + + diff --git a/day7/templates/index.html b/day7/templates/index.html index b5fae3a..bceb358 100644 --- a/day7/templates/index.html +++ b/day7/templates/index.html @@ -1,7 +1,7 @@ - + -{text} +%%text%% \ No newline at end of file diff --git a/day7/utils.py b/day7/utils.py index 0aa0709..ec0c0b4 100644 --- a/day7/utils.py +++ b/day7/utils.py @@ -13,6 +13,8 @@ BAD_REQUEST = 'HTTP/1.1 400 Bad Request' NOT_FOUND = 'HTTP/1.1 404 Not Found' SUCCESS = 'HTTP/1.1 200 OK' METHOD_NOT_ALLOWED = 'HTTP/1.1 405 Method Not Allowed' +URL_REGEX_PATTERN = re.compile(r'/((.*?/?)+)?') +HTTP_METHODS = ['GET', 'POST', 'OPTIONS', 'HEAD', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT'] def add_headers(status, html: str): @@ -27,8 +29,13 @@ def add_headers(status, html: str): def validate_url(url): - url_pattern = re.compile(r'/((.*?/?)+)?') - return bool(url_pattern.fullmatch(url)) + return bool(URL_REGEX_PATTERN.fullmatch(url)) + + +def validate_first_line(line): + methods_groups = f"({'|'.join(HTTP_METHODS)})" + first_line_pattern = re.compile(rf'{methods_groups} {URL_REGEX_PATTERN.pattern} HTTP/1\.[01]') + return bool(first_line_pattern.fullmatch(line)) def parse_cookies(cookies_line: str): @@ -41,16 +48,30 @@ def parse_cookies(cookies_line: str): d[key] = value except ValueError: raise ValueError('Wrong format of cookies') - return d def parse_headers(request_line: str): request = request_line.split('\r\n') - method, url, http_ver = request.pop(0).split() + + first_line = request.pop(0) + if validate_first_line(first_line): + method, url, http_ver = first_line + else: + # Я не знаю зачем в условии необходимо завершать работу сервера, если пришел не-HTTP запрос, + # поэтому оставлю эту строку здесь. + # raise ValueError('Wrong format of HTTP request') + raise ConnectionError('Wrong format of HTTP request') + headers = {} for line in request: - field, value = line.split(': ', maxsplit=1) + + try: + field, value = line.split(': ', maxsplit=1) + except ValueError: + # raise ValueError('Wrong format of HTTP header') + raise ConnectionError('Wrong format of HTTP request') + headers[field] = value return method, url, http_ver, headers @@ -64,7 +85,8 @@ def parse_query(query_line: str): key, value = pair.split('=', maxsplit=1) d[url_decoder(key)] = url_decoder(value) except ValueError: - raise ValueError('Wrong format of query') + # raise ValueError('Wrong format of query') + raise ConnectionError('Wrong format of query') return d -- cgit v1.2.3