diff options
| author | Andrew <saintruler@gmail.com> | 2019-05-24 18:42:16 +0400 |
|---|---|---|
| committer | Andrew <saintruler@gmail.com> | 2019-05-24 18:42:16 +0400 |
| commit | 11979f1e220587198ba05fd3c1f88d4f81195fff (patch) | |
| tree | 7161944a8935b3574ff6d044d123e9c8d1ed437f /day7 | |
| parent | 0f0e815ad1b775ff93699b695f290c562c57962f (diff) | |
Обновлена главная страница, изменен шаблонизатор.
Парсинг HTTP запросов стал более строгим. Сервер заканчивает работу при невалидном HTTP запросе.
Начата работа над документацией.
Diffstat (limited to 'day7')
| -rw-r--r-- | day7/README.md | 56 | ||||
| -rw-r--r-- | day7/backend.py | 9 | ||||
| -rw-r--r-- | day7/http_handler.py | 8 | ||||
| -rw-r--r-- | day7/templater.py | 5 | ||||
| -rw-r--r-- | day7/templates/form.html | 47 | ||||
| -rw-r--r-- | day7/templates/index.html | 4 | ||||
| -rw-r--r-- | day7/utils.py | 34 |
7 files changed, 141 insertions, 22 deletions
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 @@ -<html style="background-color: {color};"> - <body> +<html style="background-color: %%color%%;"> + +<head> + <style type="text/css"> + div { + background-color: aliceblue; + border-radius: 10px; + box-shadow: 5px 5px 5px rgba(0,0,0,0.5); + padding: 15px; + width: 400px; + margin: 20px; + } + + div form { + margin: 0; + } + + .submit_btn { + width: 100%; + } + + </style> +</head> + +<body> + <div> <form action="/show_errors" method="POST"> <input type="checkbox" name="show_errors" value="1">Show errors<br/> - <input type="submit" value="Send"><br/> + <input class="submit_btn" type="submit" value="Send"><br/> </form> + </div> + <div> <form action="/div" method="POST"> Разделить <input type="text" name="numerator"> на <input type="text" name="denominator"><br/> - <input type="submit" value="Send"><br/> + <input class="submit_btn" type="submit" value="Send"><br/> </form> + </div> + <div> <form action="/set_cookie/=" method="POST"> Изменить Cookies <input type="text" name="cookie_line"><br/> - <input type="submit" value="Send"><br/> + <input class="submit_btn" type="submit" value="Send"><br/> </form> + </div> + <div> <form action="/" method="POST"> <input type="checkbox" name="short_log" value="1">Short log<br/> - <input type="submit" value="Send"><br/> + <input class="submit_btn" type="submit" value="Send"><br/> </form> - </body> + </div> + +</body> + </html> 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 @@ -<html style="background-color: {color};"> +<html style="background-color: %%color%%;"> <body> -{text} +%%text%% </body> </html>
\ 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 |