summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew <saintruler@gmail.com>2019-05-24 18:42:16 +0400
committerAndrew <saintruler@gmail.com>2019-05-24 18:42:16 +0400
commit11979f1e220587198ba05fd3c1f88d4f81195fff (patch)
tree7161944a8935b3574ff6d044d123e9c8d1ed437f
parent0f0e815ad1b775ff93699b695f290c562c57962f (diff)
Обновлена главная страница, изменен шаблонизатор.
Парсинг HTTP запросов стал более строгим. Сервер заканчивает работу при невалидном HTTP запросе. Начата работа над документацией.
-rw-r--r--day7/README.md56
-rw-r--r--day7/backend.py9
-rw-r--r--day7/http_handler.py8
-rw-r--r--day7/templater.py5
-rw-r--r--day7/templates/form.html47
-rw-r--r--day7/templates/index.html4
-rw-r--r--day7/utils.py34
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