From 3ed8e51e46226069d96e93f854f789d7153095c4 Mon Sep 17 00:00:00 2001 From: Ilya Fomko Date: Fri, 12 May 2023 18:11:52 +0500 Subject: [PATCH 1/9] =?UTF-8?q?=D0=A1=D0=BD=D0=BE=D0=B2=D0=B0=20=D0=B4?= =?UTF-8?q?=D0=B5=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=B7=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=20=D0=BA=D0=BE=D0=B4=20=D0=B8=20=D1=81=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/http_client.py | 2 +- main.py | 1 + server/server.py | 69 ++++++++++++--- tests/main_tests.py | 196 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 11 deletions(-) diff --git a/client/http_client.py b/client/http_client.py index 38412ab..50e4084 100644 --- a/client/http_client.py +++ b/client/http_client.py @@ -75,7 +75,7 @@ def get_headers(settings: dict) -> str: cookies.append(f'{key}={value};') if len(cookies) != 0: - cookies = 'Cookie: ' + ' '.join(cookies) + cookies = 'Cookie: ' + ' '.join(cookies) cookies = cookies[:-1] cookies += '\r\n' headers.append(cookies) diff --git a/main.py b/main.py index c6f2d73..dcf4a5e 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ from server.server import HttpServer if __name__ == '__main__': + print(type(dict()) == dict) server = HttpServer() server.start() diff --git a/server/server.py b/server/server.py index 26fc6bb..4018460 100644 --- a/server/server.py +++ b/server/server.py @@ -44,15 +44,38 @@ def start(self): body = re.search(r'(?<=\r\n\r\n)(.+)', data).group(0) settings = json.loads(body) - client = HttpClient(settings) - - client_data = client.get_data() + if not HttpServer.validate_body(settings): + response = HttpServer.create_bad_request_response( + origin, + 'Вы не ввели либо url, либо тип запроса' + ) + conn.sendall(response.encode()) + else: + client = HttpClient(settings) + client_data = client.get_data() - response = self.create_response(client_data, origin) - conn.sendall(response.encode()) + response = HttpServer.create_ok_request_response(client_data, origin) + conn.sendall(response.encode()) print(f'Client with addr {addr} was disconnected') + @staticmethod + def validate_body(body: dict): + """ + Проверяет является ли тело запроса валидным + :return: возвращает либо True, либо False + """ + + if \ + body.get('url') is None or \ + body.get('request') is None or \ + type(body.get('cookie')) != dict or \ + type(body.get('headers')) != dict or \ + type(body.get('body')) != str: + return False + + return True + @staticmethod def create_option_response(origin: str): """ @@ -70,15 +93,17 @@ def create_option_response(origin: str): f"\r\n" @staticmethod - def create_response(data: str, origin: str): + def create_response(code: int, code_message: str, data: str, origin: str,): """ - Создает ответ на запрос - :param data: данные, которые будут помещены в тело запроса + Создает ответ на пользовательский запрос + :param code: код ответа + :param code_message: код ответа + :param data: данные в теле ответа (должны быть в json формате) :param origin: значение заголовка Origin в Option запросе клиента - :return: возвращает готовый ответ для клиента в формате протокола HTTP + :return: ответ """ - response = f'HTTP/1.1 200 OK\r\n' \ + response = f'HTTP/1.1 {code} {code_message}\r\n' \ f'Content-Type: application/json\r\n' \ f'Content-Length: {len(data.encode())}\r\n' \ f'Connection: keep-alive\r\n' \ @@ -91,3 +116,27 @@ def create_response(data: str, origin: str): }) return response + + @staticmethod + def create_ok_request_response(data: str, origin: str): + """ + Создает ответ на запрос с кодом успеха + :param data: данные, которые будут помещены в тело запроса + :param origin: значение заголовка Origin в Option запросе клиента + :return: возвращает готовый ответ для клиента в формате протокола HTTP + """ + + return HttpServer.create_response(200, 'OK', data, origin) + + @staticmethod + def create_bad_request_response(origin: str, message: str): + """ + Создает ответ с кодом плохого запроса (bad request) + :return: + """ + + body = dict() + + body['message'] = message + + return HttpServer.create_response(400, "BAD REQUEST", json.dumps(body), origin) diff --git a/tests/main_tests.py b/tests/main_tests.py index 3a14dca..5f283ca 100644 --- a/tests/main_tests.py +++ b/tests/main_tests.py @@ -1,6 +1,8 @@ +import json import unittest from client.http_client import HttpClient +from server.server import HttpServer from test_server import TestServer @@ -52,6 +54,200 @@ def test_create_request(self): self.assertEqual(HttpClient.create_http_request(settings), expected_result) + def test_create_request_without_cookie(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': {}, + 'headers': { + 'Host': '127.0.0.1' + }, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + expected_result = f'GET / HTTP/1.1\r\n' + \ + f'Host: 127.0.0.1\r\n\r\n' \ + f'Привет, мир!' + + self.assertEqual(HttpClient.create_http_request(settings), expected_result) + + def test_create_request_without_headers(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'headers': {}, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + expected_result = f'GET / HTTP/1.1\r\n' + \ + f'Cookie: name=ilya; surname=fomko\r\n\r\n' \ + f'Привет, мир!' + + self.assertEqual(HttpClient.create_http_request(settings), expected_result) + + def test_create_headers_string_with_cookie(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'headers': { + 'Host': '127.0.0.1', + 'Connection': 'keep-alive' + }, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + expected_result = 'Host: 127.0.0.1\r\nConnection: keep-alive\r\nCookie: name=ilya; surname=fomko\r\n' + + self.assertEqual(HttpClient.get_headers(settings), expected_result) + + def test_create_headers_string_without_cookie(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': {}, + 'headers': { + 'Host': '127.0.0.1', + 'Connection': 'keep-alive' + }, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + expected_result = 'Host: 127.0.0.1\r\nConnection: keep-alive\r\n' + + self.assertEqual(HttpClient.get_headers(settings), expected_result) + + def test_create_close_http_request(self): + settings = { + 'url': '127.0.0.1', + 'request': 'GET' + } + + expected_result = 'GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n' + + self.assertEqual(HttpClient.create_http_close_request(settings), expected_result) + + def test_validate_body_positive(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'headers': {}, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_body(settings), True) + + def test_validate_body_negative_1(self): + settings = { + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_body(settings), False) + + def test_validate_body_negative_2(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'body': 'Привет, мир!', + } + + self.assertEqual(HttpServer.validate_body(settings), False) + + def test_validate_body_negative_3(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_body(settings), False) + + def test_validate_body_negative_4(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'headers': {}, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_body(settings), False) + + def test_create_option_response(self): + origin = '127.0.0.1' + + expected_result = "HTTP/1.1 200 OK\r\n" \ + "Access-Control-Allow-Origin: 127.0.0.1\r\n" \ + "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n" \ + "Access-Control-Allow-Headers: Content-Type\r\n" \ + "Connection: keep-alive\r\n" \ + "Content-Length: 0\r\n" \ + "\r\n" + + self.assertEqual(HttpServer.create_option_response(origin), expected_result) + + def test_create_response_with_body(self): + code = 200 + code_message = 'OK' + body = '{\'message\': \'все супер! ты молодец\'}' + origin = '127.0.0.1' + + expected_result = 'HTTP/1.1 200 OK\r\n' \ + 'Content-Type: application/json\r\n' \ + f'Content-Length: {len(body.encode())}\r\n' \ + 'Connection: keep-alive\r\n' \ + 'Access-Control-Allow-Origin: 127.0.0.1' \ + '\r\n\r\n' + \ + json.dumps({'data': body}) + + self.assertEqual(HttpServer.create_response(code, code_message, body, origin), expected_result) + + def test_create_response_without_body(self): + code = 200 + code_message = 'OK' + body = '' + origin = '127.0.0.1' + + expected_result = 'HTTP/1.1 200 OK\r\n' \ + 'Content-Type: application/json\r\n' \ + f'Content-Length: {len(body.encode())}\r\n' \ + 'Connection: keep-alive\r\n' \ + 'Access-Control-Allow-Origin: 127.0.0.1' \ + '\r\n\r\n' + + self.assertEqual(HttpServer.create_response(code, code_message, body, origin), expected_result) + if __name__ == '__main__': unittest.main() From 5f056b075e0c3bf2dad4f1ea9515627eaf997750 Mon Sep 17 00:00:00 2001 From: Ilya Fomko Date: Fri, 19 May 2023 12:39:06 +0500 Subject: [PATCH 2/9] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D1=83=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D1=83=20=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D0=BE?= =?UTF-8?q?,=20=D1=82=D0=B0=D0=BA=D0=B6=D0=B5=20=D1=81=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=BA=D1=80=D0=B0=D1=81=D0=B8=D0=B2=D1=8B=D0=B9?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D0=BD=D0=B0=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B5.=20=D0=95=D1=81=D0=BB=D0=B8=20=D1=8D=D1=82=D0=BE?= =?UTF-8?q?=20=D0=B1=D1=8B=D0=BB=20GET=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81,=20=D1=82=D0=BE=20=D0=B2=20=D0=BE=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=B5=20=D0=BF=D1=80=D0=B8=D1=81=D1=8B=D0=BB=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=BA=D0=BE=D0=B4=20=D0=BE=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D1=82=D0=B0=20=D0=B8=20=D0=BA=D0=BE=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B2=D0=BE=20=D0=B2=D1=85=D0=BE=D0=B6=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BF=D0=BE=D0=B4=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D1=87=D0=BA=D0=B8=20=D0=B2=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BA=D1=83=20=D1=81=20=D1=80=D0=B5=D0=B7=D1=83=D0=BB?= =?UTF-8?q?=D1=8C=D1=82=D0=B0=D1=82=D0=BE=D0=BC=20=D0=BE=D1=82=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B0.=20=D0=92=20=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D1=81=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D0=B0=D1=8F=D1=85=20=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=BE?= =?UTF-8?q?=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82=20=D0=BE=D1=82=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B5=D1=80=D0=B0.=20=D0=A2=D0=B0=D0=BA=D0=B6?= =?UTF-8?q?=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D0=BE=D1=87=D0=BA=D1=83,=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D1=80=D0=B0=D1=8F=20=D0=B7=D0=B0=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D1=88=D0=B8=D0=B2=D0=B0=D0=B5=D1=82=20=D1=8D=D1=82=D1=83?= =?UTF-8?q?=20=D1=81=D1=82=D1=80=D0=BE=D1=87=D0=BA=D1=83,=20=D0=B5=D1=81?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=B2=D1=8B=D0=B1=D1=80=D0=B0=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B0=20GET=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/http_client.py | 8 ++- main.py | 1 - resources/index.html | 140 ++++++++++++++++++++++-------------------- resources/main.css | 37 ++++++++--- resources/main.js | 39 +++++++++--- server/server.py | 58 +++++++++++++---- 6 files changed, 184 insertions(+), 99 deletions(-) diff --git a/client/http_client.py b/client/http_client.py index 50e4084..a2aa830 100644 --- a/client/http_client.py +++ b/client/http_client.py @@ -6,7 +6,7 @@ def __init__(self, settings: dict): self.__settings = settings self.__HOST = self.__settings.get("url") - self.__PORT = int(self.__settings.get("port")) + self.__PORT = self.__settings.get("port") def get_data(self) -> str: """ @@ -21,7 +21,11 @@ def get_data(self) -> str: timeout = self.__settings.get("timeout") s.settimeout(timeout) - s.connect((self.__HOST, self.__PORT)) + + try: + s.connect((self.__HOST, int(self.__PORT))) + except ValueError: + return 'Неправильный url адрес или port' request = self.create_http_request(self.__settings).encode() diff --git a/main.py b/main.py index dcf4a5e..c6f2d73 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,6 @@ from server.server import HttpServer if __name__ == '__main__': - print(type(dict()) == dict) server = HttpServer() server.start() diff --git a/resources/index.html b/resources/index.html index cb57976..f10db42 100644 --- a/resources/index.html +++ b/resources/index.html @@ -20,25 +20,26 @@ -
-

HTTP-Client

-

Ошибка! Вы не ввели либо url, либо port, либо тип запроса!

-
-
-
- - -
-
- - -
-
- - -
-
-
Тип запроса:
+
+

HTTP-Client

+

Ошибка! Вы не ввели либо url, либо port, либо тип запроса!

+
+ +
+ + +
+
+ + +
+
+ + +
+
+
Тип запроса:
+
@@ -67,59 +68,68 @@

HTTP-Client

-
-
-
- -

- * - Заголовки должны быть записаны в формате:
- Заголовок: значение; - И обязательно должны быть разделены точкой с запятой -

-
- +
+
+ +

+ * + Заголовки должны быть записаны в формате:
+ Заголовок: значение; + И обязательно должны быть разделены точкой с запятой +

-
- - + +
+ -
-
-

Результат запроса:

-

+
+
+ + +
+ + +
+
+
+

Результат запроса:

+

+
diff --git a/resources/main.css b/resources/main.css index 3d958af..adea90c 100644 --- a/resources/main.css +++ b/resources/main.css @@ -115,6 +115,24 @@ textarea::placeholder { width: 35%; } +.request_btns { + display: flex; + align-items: flex-start; + justify-content: space-between; + + width: 100%; +} + +.get_form { + display: none; + + margin-left: 15px; +} + +.get_form__active { + display: block; +} + /* Wrappers styles */ .form_wrapper { @@ -255,13 +273,7 @@ textarea::placeholder { /* Result styles */ .result-container { - flex-direction: column; -} - -.result__text { - font-size: 15px; - color: #333; - font-weight: 400; + flex-direction: column; } .result { @@ -281,8 +293,13 @@ textarea::placeholder { } .result__text { - width: 100%; + font-size: 15px; + color: #333; + font-weight: 400; + + width: 100%; - hyphens: auto; - white-space: pre-wrap; + hyphens: auto; + white-space: pre-wrap; + word-wrap: break-word; } \ No newline at end of file diff --git a/resources/main.js b/resources/main.js index e0bbda5..c15c682 100644 --- a/resources/main.js +++ b/resources/main.js @@ -49,6 +49,10 @@ const onSubmit = (event) => { const body = JSON.stringify(fields) + if (fields.request !== 'get') { + fields.get_form = '' + } + fetch('http://127.0.0.1:8080/', { method: 'post', headers: { @@ -58,16 +62,21 @@ const onSubmit = (event) => { }) .then(response => response.text()) .then(data => { - const result_text = document.querySelector('.result__text'); - result_text.textContent = data; + const result_text = document.querySelector('.result__text'); + + console.log(data) - const result = document.querySelector('.result'); + const jsonResponse = JSON.parse(data) - if (!result.classList.contains('result-active')) { - result.classList.add('result-active') + result_text.textContent = JSON.stringify(jsonResponse) + + const result = document.querySelector('.result'); + + if (!result.classList.contains('result-active')) { + result.classList.add('result-active') + } } - } - ); + ); return true } @@ -117,6 +126,18 @@ const parse = (headers) => { return response.length === 0 ? {} : response } -window.addEventListener('load', () => { +const radioBtnChecker = e => { + if (e.target.tagName.toLowerCase() === 'input' && e.target.id.toLowerCase() === 'get-request') { + document.querySelector('#get_form').classList.add('get_form__active'); + return null + } + + document.querySelector('#get_form').classList.remove('get_form__active'); +} + +const main = () => { document.querySelector('.form').addEventListener('submit', onSubmit) -}) \ No newline at end of file + document.querySelector('.request-btn_container').addEventListener('click', radioBtnChecker) +} + +window.addEventListener('load', main) \ No newline at end of file diff --git a/server/server.py b/server/server.py index 4018460..84e71c8 100644 --- a/server/server.py +++ b/server/server.py @@ -29,7 +29,8 @@ def start(self): print(f'Client connected by addr: {addr}') with conn: - data = conn.recv(8190).decode() + data = conn.recv(8192).decode() + origin = None if data.startswith('OPTIONS / HTTP/1.1'): @@ -49,16 +50,48 @@ def start(self): origin, 'Вы не ввели либо url, либо тип запроса' ) - conn.sendall(response.encode()) + + HttpServer.send_data(conn, response) else: client = HttpClient(settings) client_data = client.get_data() - response = HttpServer.create_ok_request_response(client_data, origin) - conn.sendall(response.encode()) + if client_data == 'Неправильный url адрес или port': + response = HttpServer.create_bad_request_response(origin, client_data) + HttpServer.send_data(conn, response) + else: + data = client_data + + if settings.get('request').lower() == 'get' and len(settings.get('get_form')) != 0: + matches = re.finditer(settings.get('get_form'), client_data, re.IGNORECASE) + data = [match.start() for match in matches] + + response = HttpServer.create_ok_request_response(data, origin) + HttpServer.send_data(conn, response) print(f'Client with addr {addr} was disconnected') + @staticmethod + def send_data(conn: socket, response: str) -> int: + """ + Отправляет данные на сервер + :param conn: подключение, по которому нужно отправить эти данные + :param response: отправляемые данные + :return: количество отправленных байт + """ + data_to_send = response.encode('utf-8') + bytes_sent = 0 + + while bytes_sent < len(data_to_send): + sent = conn.send(data_to_send[bytes_sent:]) + + if sent == 0: + break + + bytes_sent += sent + + return bytes_sent + @staticmethod def validate_body(body: dict): """ @@ -108,12 +141,7 @@ def create_response(code: int, code_message: str, data: str, origin: str,): f'Content-Length: {len(data.encode())}\r\n' \ f'Connection: keep-alive\r\n' \ f'Access-Control-Allow-Origin: {origin}' \ - f'\r\n\r\n' - - if len(data) != 0: - response += json.dumps({ - 'data': data - }) + f'\r\n\r\n{data}' return response @@ -126,7 +154,12 @@ def create_ok_request_response(data: str, origin: str): :return: возвращает готовый ответ для клиента в формате протокола HTTP """ - return HttpServer.create_response(200, 'OK', data, origin) + body = dict() + + body['data'] = data + body['code'] = 200 + + return HttpServer.create_response(body['code'], 'OK', json.dumps(body), origin) @staticmethod def create_bad_request_response(origin: str, message: str): @@ -138,5 +171,6 @@ def create_bad_request_response(origin: str, message: str): body = dict() body['message'] = message + body['code'] = 400 - return HttpServer.create_response(400, "BAD REQUEST", json.dumps(body), origin) + return HttpServer.create_response(body['code'], 'BAD REQUEST', json.dumps(body), origin) From 4ba13f3410d4cbcb2102165425c2a64238d36044 Mon Sep 17 00:00:00 2001 From: Ilya Fomko Date: Mon, 22 May 2023 18:45:52 +0500 Subject: [PATCH 3/9] =?UTF-8?q?=D0=A0=D0=B5=D1=81=D1=82=D1=80=D1=83=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC=D0=BC=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ client/http_client.py => http_client.py | 0 main.py | 2 +- resources/main.js | 2 -- server/server.py => server.py | 2 +- tests/main_tests.py => test_main.py | 12 +++++++----- tests/test_server.py => testing_server.py | 0 7 files changed, 12 insertions(+), 9 deletions(-) rename client/http_client.py => http_client.py (100%) rename server/server.py => server.py (99%) rename tests/main_tests.py => test_main.py (97%) rename tests/test_server.py => testing_server.py (100%) diff --git a/.gitignore b/.gitignore index fb61ed9..3d45ddf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ # created by virtualenv automatically env/ .idea/ +Pipfile +.pytest_cache +.coverage \ No newline at end of file diff --git a/client/http_client.py b/http_client.py similarity index 100% rename from client/http_client.py rename to http_client.py diff --git a/main.py b/main.py index c6f2d73..e93ee1f 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ -from server.server import HttpServer +from server import HttpServer if __name__ == '__main__': server = HttpServer() diff --git a/resources/main.js b/resources/main.js index c15c682..8c157c9 100644 --- a/resources/main.js +++ b/resources/main.js @@ -64,8 +64,6 @@ const onSubmit = (event) => { .then(data => { const result_text = document.querySelector('.result__text'); - console.log(data) - const jsonResponse = JSON.parse(data) result_text.textContent = JSON.stringify(jsonResponse) diff --git a/server/server.py b/server.py similarity index 99% rename from server/server.py rename to server.py index 84e71c8..695781a 100644 --- a/server/server.py +++ b/server.py @@ -2,7 +2,7 @@ import socket import json -from client.http_client import HttpClient +from http_client import HttpClient class HttpServer: diff --git a/tests/main_tests.py b/test_main.py similarity index 97% rename from tests/main_tests.py rename to test_main.py index 5f283ca..8323df6 100644 --- a/tests/main_tests.py +++ b/test_main.py @@ -1,9 +1,9 @@ import json import unittest -from client.http_client import HttpClient -from server.server import HttpServer -from test_server import TestServer +from http_client import HttpClient +from server import HttpServer +from testing_server import TestServer # ToDo: сделать тесты ещё для методов класса HttpClient: create_http_request, create_http_close_request и get_headers @@ -220,7 +220,9 @@ def test_create_option_response(self): def test_create_response_with_body(self): code = 200 code_message = 'OK' - body = '{\'message\': \'все супер! ты молодец\'}' + body = json.dumps({ + 'message': 'все супер! ты молодец' + }) origin = '127.0.0.1' expected_result = 'HTTP/1.1 200 OK\r\n' \ @@ -229,7 +231,7 @@ def test_create_response_with_body(self): 'Connection: keep-alive\r\n' \ 'Access-Control-Allow-Origin: 127.0.0.1' \ '\r\n\r\n' + \ - json.dumps({'data': body}) + f'{body}' self.assertEqual(HttpServer.create_response(code, code_message, body, origin), expected_result) diff --git a/tests/test_server.py b/testing_server.py similarity index 100% rename from tests/test_server.py rename to testing_server.py From 8419d7be6f555e922b5faf91fdd1f36427a9211e Mon Sep 17 00:00:00 2001 From: Ilya Fomko Date: Thu, 25 May 2023 17:03:51 +0500 Subject: [PATCH 4/9] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B2=D1=81=D0=B5=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BF=D1=83=D0=BB=D0=BB=20?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D0=B2=D0=B5=D1=81=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- enums/client_messages_response.py | 5 ++++ enums/requests_names.py | 9 ++++++ enums/server_validating_messages.py | 10 +++++++ http_client.py | 20 +++++++------ server.py | 45 ++++++++++++++++++++--------- 5 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 enums/client_messages_response.py create mode 100644 enums/requests_names.py create mode 100644 enums/server_validating_messages.py diff --git a/enums/client_messages_response.py b/enums/client_messages_response.py new file mode 100644 index 0000000..082e167 --- /dev/null +++ b/enums/client_messages_response.py @@ -0,0 +1,5 @@ +import enum + + +class ClientMessagesResponse(enum.Enum): + incorrect_url_or_port = ' url port' diff --git a/enums/requests_names.py b/enums/requests_names.py new file mode 100644 index 0000000..6d2b844 --- /dev/null +++ b/enums/requests_names.py @@ -0,0 +1,9 @@ +import enum + + +class RequestsNames(enum.Enum): + get = 'get' + post = 'post' + patch = 'patch' + delete = 'delete' + put = 'put' diff --git a/enums/server_validating_messages.py b/enums/server_validating_messages.py new file mode 100644 index 0000000..c420d84 --- /dev/null +++ b/enums/server_validating_messages.py @@ -0,0 +1,10 @@ +import enum + + +class ServerValidatingMessages(enum.Enum): + incorrect_url = ' url-' + incorrect_type_request = ' ' + incorrect_format_cookie = ' cookie: cookie ' + incorrect_format_headers = ' : ' + incorrect_body_type = ' : ' + is_validated = ' ' diff --git a/http_client.py b/http_client.py index a2aa830..84049f2 100644 --- a/http_client.py +++ b/http_client.py @@ -1,12 +1,14 @@ import socket +from enums.client_messages_response import ClientMessagesResponse + class HttpClient: def __init__(self, settings: dict): - self.__settings = settings + self._settings = settings - self.__HOST = self.__settings.get("url") - self.__PORT = self.__settings.get("port") + self._HOST = self._settings.get("url") + self._PORT = self._settings.get("port") def get_data(self) -> str: """ @@ -17,17 +19,17 @@ def get_data(self) -> str: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: timeout = 10 - if self.__settings.get("timeout") is not None: - timeout = self.__settings.get("timeout") + if self._settings.get("timeout") is not None: + timeout = self._settings.get("timeout") s.settimeout(timeout) try: - s.connect((self.__HOST, int(self.__PORT))) + s.connect((self._HOST, int(self._PORT))) except ValueError: - return 'Неправильный url адрес или port' + return str(ClientMessagesResponse.incorrect_url_or_port.value) - request = self.create_http_request(self.__settings).encode() + request = self.create_http_request(self._settings).encode() try: s.sendall(request) @@ -49,7 +51,7 @@ def get_data(self) -> str: except Exception as e: print('log: ' + str(e)) - close_request = self.create_http_close_request(self.__settings).encode() + close_request = self.create_http_close_request(self._settings).encode() try: s.sendall(close_request) diff --git a/server.py b/server.py index 695781a..646e6ea 100644 --- a/server.py +++ b/server.py @@ -2,6 +2,9 @@ import socket import json +from enums.client_messages_response import ClientMessagesResponse +from enums.requests_names import RequestsNames +from enums.server_validating_messages import ServerValidatingMessages from http_client import HttpClient @@ -45,10 +48,12 @@ def start(self): body = re.search(r'(?<=\r\n\r\n)(.+)', data).group(0) settings = json.loads(body) - if not HttpServer.validate_body(settings): + settings_is_validated = HttpServer.validate_body(settings) + + if not settings_is_validated[0]: response = HttpServer.create_bad_request_response( origin, - 'Вы не ввели либо url, либо тип запроса' + settings_is_validated[1] ) HttpServer.send_data(conn, response) @@ -56,13 +61,17 @@ def start(self): client = HttpClient(settings) client_data = client.get_data() - if client_data == 'Неправильный url адрес или port': + if client_data == ClientMessagesResponse.incorrect_url_or_port.value: response = HttpServer.create_bad_request_response(origin, client_data) HttpServer.send_data(conn, response) else: data = client_data - if settings.get('request').lower() == 'get' and len(settings.get('get_form')) != 0: + if ( + settings.get('request').lower() == RequestsNames.get.value + and + len(settings.get('get_form')) != 0 + ): matches = re.finditer(settings.get('get_form'), client_data, re.IGNORECASE) data = [match.start() for match in matches] @@ -93,21 +102,29 @@ def send_data(conn: socket, response: str) -> int: return bytes_sent @staticmethod - def validate_body(body: dict): + def validate_body(body: dict) -> tuple: """ Проверяет является ли тело запроса валидным - :return: возвращает либо True, либо False + :return: возвращает tuple, где первый параметр - это true или false: результат валидности, + а второй сообщение описывающее результат валидации """ - if \ - body.get('url') is None or \ - body.get('request') is None or \ - type(body.get('cookie')) != dict or \ - type(body.get('headers')) != dict or \ - type(body.get('body')) != str: - return False + if body.get('url') is None: + return tuple(iterable=[False, ServerValidatingMessages.incorrect_url.value]) + + if body.get('request') is None: + return tuple(iterable=[False, ServerValidatingMessages.incorrect_type_request.value]) + + if not isinstance(body.get('cookie'), dict): + return tuple(iterable=[False, ServerValidatingMessages.incorrect_format_cookie.value]) + + if not isinstance(body.get('headers'), dict): + return tuple(iterable=[False, ServerValidatingMessages.incorrect_format_headers.value]) + + if not isinstance(body.get('body'), str): + return tuple(iterable=[False, ServerValidatingMessages.incorrect_body_type.value]) - return True + return tuple(iterable=[True, ServerValidatingMessages.is_validated.value]) @staticmethod def create_option_response(origin: str): From 5e9a28443d0396e0b2139bbbaa675c9d779db560 Mon Sep 17 00:00:00 2001 From: Ilya Fomko Date: Thu, 25 May 2023 23:00:49 +0500 Subject: [PATCH 5/9] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B2=D1=81=D0=B5=20=D1=82=D0=B5=D0=BA=D1=81=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D0=B0=D0=BD=D0=B3=D0=BB=D0=B8=D0=B9?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=D0=B9=20=D1=8F=D0=B7=D1=8B=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- enums/client_messages_response.py | 2 +- enums/server_validating_messages.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/enums/client_messages_response.py b/enums/client_messages_response.py index 082e167..8713b69 100644 --- a/enums/client_messages_response.py +++ b/enums/client_messages_response.py @@ -2,4 +2,4 @@ class ClientMessagesResponse(enum.Enum): - incorrect_url_or_port = ' url port' + incorrect_url_or_port = 'Incorrect URL-address or port' diff --git a/enums/server_validating_messages.py b/enums/server_validating_messages.py index c420d84..dea5260 100644 --- a/enums/server_validating_messages.py +++ b/enums/server_validating_messages.py @@ -2,9 +2,9 @@ class ServerValidatingMessages(enum.Enum): - incorrect_url = ' url-' - incorrect_type_request = ' ' - incorrect_format_cookie = ' cookie: cookie ' - incorrect_format_headers = ' : ' - incorrect_body_type = ' : ' - is_validated = ' ' + incorrect_url = 'You did not write URL-address' + incorrect_type_request = 'You did not write type of request' + incorrect_format_cookie = 'Incorrect format cookie: type of object which takes cookie must be dictionary' + incorrect_format_headers = 'Incorrect format headers: type of object which takes headers must be dictionary' + incorrect_body_type = 'Incorrect format of body: type of object which takes body is string' + is_validated = 'All parameters is valid' From 1a3d04a8303fde7416f954bc6cacdf6998d3ae6e Mon Sep 17 00:00:00 2001 From: Ilya Fomko Date: Fri, 26 May 2023 10:36:19 +0500 Subject: [PATCH 6/9] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B1=D0=B0=D0=B3=20=D1=81=20tuple?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 1 - server.py | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/main.py b/main.py index e93ee1f..33614a6 100644 --- a/main.py +++ b/main.py @@ -4,4 +4,3 @@ if __name__ == '__main__': server = HttpServer() server.start() - diff --git a/server.py b/server.py index 646e6ea..52f8fdc 100644 --- a/server.py +++ b/server.py @@ -110,21 +110,21 @@ def validate_body(body: dict) -> tuple: """ if body.get('url') is None: - return tuple(iterable=[False, ServerValidatingMessages.incorrect_url.value]) + return False, ServerValidatingMessages.incorrect_url.value if body.get('request') is None: - return tuple(iterable=[False, ServerValidatingMessages.incorrect_type_request.value]) + return False, ServerValidatingMessages.incorrect_type_request.value if not isinstance(body.get('cookie'), dict): - return tuple(iterable=[False, ServerValidatingMessages.incorrect_format_cookie.value]) + return False, ServerValidatingMessages.incorrect_format_cookie.value if not isinstance(body.get('headers'), dict): - return tuple(iterable=[False, ServerValidatingMessages.incorrect_format_headers.value]) + return False, ServerValidatingMessages.incorrect_format_headers.value if not isinstance(body.get('body'), str): - return tuple(iterable=[False, ServerValidatingMessages.incorrect_body_type.value]) + return False, ServerValidatingMessages.incorrect_body_type.value - return tuple(iterable=[True, ServerValidatingMessages.is_validated.value]) + return True, ServerValidatingMessages.is_validated.value @staticmethod def create_option_response(origin: str): From 25edf34b16a3c9dadeac31cb8139139cf136a254 Mon Sep 17 00:00:00 2001 From: iffomko Date: Wed, 31 May 2023 02:32:57 +0500 Subject: [PATCH 7/9] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20=D0=BF=D1=80=D0=BE=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D0=BC=D0=BC=D1=8B:=20=D1=82=D0=BE=D1=87=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=D1=85=D0=BE=D0=B4=D0=B0,=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=B0,=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http_client.py | 49 ++++-- server.py => http_server.py | 102 +++++++----- main.py | 16 +- testing_server.py | 34 ---- tests/__init__.py | 0 test_main.py => tests/test_http_client.py | 150 +++++------------ tests/test_http_server.py | 186 ++++++++++++++++++++++ tests/test_main.py | 28 ++++ 8 files changed, 371 insertions(+), 194 deletions(-) rename server.py => http_server.py (66%) delete mode 100644 testing_server.py create mode 100644 tests/__init__.py rename test_main.py => tests/test_http_client.py (53%) create mode 100644 tests/test_http_server.py create mode 100644 tests/test_main.py diff --git a/http_client.py b/http_client.py index 84049f2..39e8d27 100644 --- a/http_client.py +++ b/http_client.py @@ -26,7 +26,7 @@ def get_data(self) -> str: try: s.connect((self._HOST, int(self._PORT))) - except ValueError: + except (ValueError, socket.gaierror): return str(ClientMessagesResponse.incorrect_url_or_port.value) request = self.create_http_request(self._settings).encode() @@ -39,22 +39,16 @@ def get_data(self) -> str: response = b"" try: - while True: - data = s.recv(4096) - - if not data: - break - - response += data + response = self.receive_data(s) except socket.timeout: pass except Exception as e: print('log: ' + str(e)) - close_request = self.create_http_close_request(self._settings).encode() + close_request = self.create_http_close_request(self._settings) try: - s.sendall(close_request) + self.send_data(conn=s, data=close_request) except Exception as e: print('log: ' + str(e)) @@ -62,6 +56,41 @@ def get_data(self) -> str: return response + @staticmethod + def receive_data(conn: socket) -> bytes: + response = b"" + + while True: + data = conn.recv(4096) + + if not data: + break + + response += data + + return response + + @staticmethod + def send_data(conn: socket, data: str) -> int: + """ + Отправляет данные клиенту + :param conn: подключение, по которому нужно отправить эти данные + :param data: отправляемые данные + :return: количество отправленных байт + """ + data_to_send = data.encode('utf-8') + bytes_sent = 0 + + while bytes_sent < len(data_to_send): + sent = conn.send(data_to_send[bytes_sent:]) + + if sent == 0: + break + + bytes_sent += sent + + return bytes_sent + @staticmethod def get_headers(settings: dict) -> str: """ diff --git a/server.py b/http_server.py similarity index 66% rename from server.py rename to http_server.py index 52f8fdc..499d463 100644 --- a/server.py +++ b/http_server.py @@ -17,68 +17,88 @@ def __init__(self): self.__HOST = '127.0.0.1' self.__PORT = 8080 + self._server = None + def start(self): """ Запускает сервер """ - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((self.__HOST, self.__PORT)) - s.listen() + try: + self._create_server() + except OSError as e: + self.stop() + print(e) + + if self._server is None: + return + + self._server.listen() + + while True: + if self._server is None: + break + + conn, addr = self._server.accept() - while True: - conn, addr = s.accept() + print(f'Client connected by addr: {addr}') - print(f'Client connected by addr: {addr}') + with conn: + data = conn.recv(8192).decode() - with conn: - data = conn.recv(8192).decode() + origin = None - origin = None + if data.startswith('OPTIONS / HTTP/1.1'): + origin = re.search(r'(?<=Origin: )(.+?)(?=\r\n)', data).group(0) - if data.startswith('OPTIONS / HTTP/1.1'): - origin = re.search(r'(?<=Origin: )(.+?)(?=\r\n)', data).group(0) + response = self.create_option_response(origin) - response = self.create_option_response(origin) + conn.sendall(response.encode()) - conn.sendall(response.encode()) + data = conn.recv(8190).decode() - data = conn.recv(8190).decode() + body = re.search(r'(?<=\r\n\r\n)(.+)', data).group(0) + settings = json.loads(body) - body = re.search(r'(?<=\r\n\r\n)(.+)', data).group(0) - settings = json.loads(body) + settings_is_validated = HttpServer.validate_params(settings) - settings_is_validated = HttpServer.validate_body(settings) + if not settings_is_validated[0]: + response = HttpServer.create_bad_request_response( + origin, + settings_is_validated[1] + ) - if not settings_is_validated[0]: - response = HttpServer.create_bad_request_response( - origin, - settings_is_validated[1] - ) + HttpServer.send_data(conn, response) + else: + client = HttpClient(settings) + client_data = client.get_data() + if client_data == ClientMessagesResponse.incorrect_url_or_port.value: + response = HttpServer.create_bad_request_response(origin, client_data) HttpServer.send_data(conn, response) else: - client = HttpClient(settings) - client_data = client.get_data() + data = client_data - if client_data == ClientMessagesResponse.incorrect_url_or_port.value: - response = HttpServer.create_bad_request_response(origin, client_data) - HttpServer.send_data(conn, response) - else: - data = client_data + if ( + settings.get('request').lower() == RequestsNames.get.value + and + len(settings.get('get_form')) != 0 + ): + matches = re.finditer(settings.get('get_form'), client_data, re.IGNORECASE) + data = [match.start() for match in matches] + + response = HttpServer.create_ok_request_response(data, origin) + HttpServer.send_data(conn, response) - if ( - settings.get('request').lower() == RequestsNames.get.value - and - len(settings.get('get_form')) != 0 - ): - matches = re.finditer(settings.get('get_form'), client_data, re.IGNORECASE) - data = [match.start() for match in matches] + print(f'Client with addr {addr} was disconnected') - response = HttpServer.create_ok_request_response(data, origin) - HttpServer.send_data(conn, response) + def _create_server(self): + self._server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._server.bind((self.__HOST, self.__PORT)) - print(f'Client with addr {addr} was disconnected') + def stop(self): + self._server.close() + self._server = None @staticmethod def send_data(conn: socket, response: str) -> int: @@ -102,9 +122,9 @@ def send_data(conn: socket, response: str) -> int: return bytes_sent @staticmethod - def validate_body(body: dict) -> tuple: + def validate_params(body: dict) -> tuple: """ - Проверяет является ли тело запроса валидным + Проверяет являются ли параметры запроса валидными :return: возвращает tuple, где первый параметр - это true или false: результат валидности, а второй сообщение описывающее результат валидации """ diff --git a/main.py b/main.py index 33614a6..7ad8a3e 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,16 @@ +from http_server import HttpServer + + +class Main: + def __init__(self): + self._server = HttpServer() + + def run(self): + self._server.start() + + def finish(self): + self._server.stop() -from server import HttpServer if __name__ == '__main__': - server = HttpServer() - server.start() + Main().run() diff --git a/testing_server.py b/testing_server.py deleted file mode 100644 index e22e07c..0000000 --- a/testing_server.py +++ /dev/null @@ -1,34 +0,0 @@ -import socket - -from threading import Thread - - -class TestServer: - def __init__(self): - super().__init__() - self.__HOST = '127.0.0.1' - self.__PORT = 4444 - - def __run_server(self): - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(15) - s.bind((self.__HOST, self.__PORT)) - - s.listen() - - conn, addr = s.accept() - - with conn: - try: - while True: - data = conn.recv(4096) - - if not data: - break - - conn.sendall(data) - except Exception as e: - print('log: ' + str(e)) - - def test(self): - Thread(target=self.__run_server).start() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_main.py b/tests/test_http_client.py similarity index 53% rename from test_main.py rename to tests/test_http_client.py index 8323df6..6fd7951 100644 --- a/test_main.py +++ b/tests/test_http_client.py @@ -1,37 +1,11 @@ -import json import unittest +from unittest.mock import MagicMock +from enums.client_messages_response import ClientMessagesResponse from http_client import HttpClient -from server import HttpServer -from testing_server import TestServer -# ToDo: сделать тесты ещё для методов класса HttpClient: create_http_request, create_http_close_request и get_headers -# ToDo: а также сделать тесты для класса HttpServer: create_option_response и create_response - -class Tests(unittest.TestCase): - def test_client(self): - settings = { - 'url': '127.0.0.1', - 'port': 4444, - 'cookie': { - 'name': 'ilya', - 'surname': 'fomko' - }, - 'headers': { - 'Host': '127.0.0.1' - }, - 'body': 'Привет, мир!', - 'request': 'GET' - } - - client = HttpClient(settings) - TestServer().test() - - result = client.get_data() - - self.assertEqual(HttpClient.create_http_request(settings), result) - +class TestClient(unittest.TestCase): def test_create_request(self): settings = { 'url': '127.0.0.1', @@ -54,6 +28,7 @@ def test_create_request(self): self.assertEqual(HttpClient.create_http_request(settings), expected_result) + # def test_create_request_without_cookie(self): settings = { 'url': '127.0.0.1', @@ -138,7 +113,7 @@ def test_create_close_http_request(self): self.assertEqual(HttpClient.create_http_close_request(settings), expected_result) - def test_validate_body_positive(self): + def test_create_close_request(self): settings = { 'url': '127.0.0.1', 'port': 4444, @@ -146,109 +121,72 @@ def test_validate_body_positive(self): 'name': 'ilya', 'surname': 'fomko' }, - 'headers': {}, + 'headers': { + 'Host': '127.0.0.1' + }, 'body': 'Привет, мир!', 'request': 'GET' } - self.assertEqual(HttpServer.validate_body(settings), True) + expected_result = 'GET / HTTP/1.1\r\n' \ + 'Host: 127.0.0.1\r\n' \ + 'Connection: close\r\n\r\n' + + self.assertEqual(expected_result, HttpClient.create_http_close_request(settings)) - def test_validate_body_negative_1(self): + def test_create_headers(self): settings = { + 'url': '127.0.0.1', 'port': 4444, 'cookie': { 'name': 'ilya', 'surname': 'fomko' }, + 'headers': { + 'Host': '127.0.0.1' + }, 'body': 'Привет, мир!', 'request': 'GET' } - self.assertEqual(HttpServer.validate_body(settings), False) + expected_result = 'Host: 127.0.0.1\r\nCookie: name=ilya; surname=fomko\r\n' - def test_validate_body_negative_2(self): - settings = { - 'url': '127.0.0.1', - 'port': 4444, - 'cookie': { - 'name': 'ilya', - 'surname': 'fomko' - }, - 'body': 'Привет, мир!', - } + self.assertEqual(expected_result, HttpClient.get_headers(settings)) + + def test_receive_data(self): + socket = MagicMock() + socket.recv.side_effect = [b'test_data', None] + + expected_result = b'test_data' + + self.assertEqual(expected_result, HttpClient.receive_data(socket)) + + def test_send_data(self): + data = 'test send data' + + expected_result = len(data.encode()) + + socket = MagicMock() + socket.send.side_effect = [expected_result, 0] - self.assertEqual(HttpServer.validate_body(settings), False) + self.assertEqual(expected_result, HttpClient.send_data(socket, data)) - def test_validate_body_negative_3(self): + def test_connect_with_host_or_port_exc(self): settings = { - 'url': '127.0.0.1', - 'port': 4444, + 'url': 'xcvx', + 'port': -234, 'cookie': { 'name': 'ilya', 'surname': 'fomko' }, + 'headers': { + 'Host': '127.0.0.1' + }, 'body': 'Привет, мир!', 'request': 'GET' } - self.assertEqual(HttpServer.validate_body(settings), False) - - def test_validate_body_negative_4(self): - settings = { - 'url': '127.0.0.1', - 'port': 4444, - 'headers': {}, - 'body': 'Привет, мир!', - 'request': 'GET' - } - - self.assertEqual(HttpServer.validate_body(settings), False) - - def test_create_option_response(self): - origin = '127.0.0.1' - - expected_result = "HTTP/1.1 200 OK\r\n" \ - "Access-Control-Allow-Origin: 127.0.0.1\r\n" \ - "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n" \ - "Access-Control-Allow-Headers: Content-Type\r\n" \ - "Connection: keep-alive\r\n" \ - "Content-Length: 0\r\n" \ - "\r\n" - - self.assertEqual(HttpServer.create_option_response(origin), expected_result) - - def test_create_response_with_body(self): - code = 200 - code_message = 'OK' - body = json.dumps({ - 'message': 'все супер! ты молодец' - }) - origin = '127.0.0.1' - - expected_result = 'HTTP/1.1 200 OK\r\n' \ - 'Content-Type: application/json\r\n' \ - f'Content-Length: {len(body.encode())}\r\n' \ - 'Connection: keep-alive\r\n' \ - 'Access-Control-Allow-Origin: 127.0.0.1' \ - '\r\n\r\n' + \ - f'{body}' - - self.assertEqual(HttpServer.create_response(code, code_message, body, origin), expected_result) - - def test_create_response_without_body(self): - code = 200 - code_message = 'OK' - body = '' - origin = '127.0.0.1' - - expected_result = 'HTTP/1.1 200 OK\r\n' \ - 'Content-Type: application/json\r\n' \ - f'Content-Length: {len(body.encode())}\r\n' \ - 'Connection: keep-alive\r\n' \ - 'Access-Control-Allow-Origin: 127.0.0.1' \ - '\r\n\r\n' - - self.assertEqual(HttpServer.create_response(code, code_message, body, origin), expected_result) + self.assertEqual(str(ClientMessagesResponse.incorrect_url_or_port.value), HttpClient(settings).get_data()) if __name__ == '__main__': diff --git a/tests/test_http_server.py b/tests/test_http_server.py new file mode 100644 index 0000000..d44c7f2 --- /dev/null +++ b/tests/test_http_server.py @@ -0,0 +1,186 @@ +import json +import unittest +from unittest.mock import MagicMock + +from http_server import HttpServer + + +class TestServer(unittest.TestCase): + def test_validate_params_positive(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'headers': {}, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_params(settings)[0], True) + + def test_validate_params_negative_1(self): + settings = { + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_params(settings)[0], False) + + def test_validate_params_negative_2(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'body': 'Привет, мир!', + } + + self.assertEqual(HttpServer.validate_params(settings)[0], False) + + def test_validate_params_negative_3(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': { + 'name': 'ilya', + 'surname': 'fomko' + }, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_params(settings)[0], False) + + def test_validate_params_negative_4(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'headers': {}, + 'body': 'Привет, мир!', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_params(settings)[0], False) + + def test_validate_params_negative_5(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'headers': {}, + 'body': 234, + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_params(settings)[0], False) + + def test_validate_params_negative_6(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'headers': 23423, + 'body': '', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_params(settings)[0], False) + + def test_validate_params_negative_7(self): + settings = { + 'url': '127.0.0.1', + 'port': 4444, + 'cookie': 1234, + 'headers': {}, + 'body': '', + 'request': 'GET' + } + + self.assertEqual(HttpServer.validate_params(settings)[0], False) + + def test_create_option_response(self): + origin = '127.0.0.1' + + expected_result = "HTTP/1.1 200 OK\r\n" \ + "Access-Control-Allow-Origin: 127.0.0.1\r\n" \ + "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n" \ + "Access-Control-Allow-Headers: Content-Type\r\n" \ + "Connection: keep-alive\r\n" \ + "Content-Length: 0\r\n" \ + "\r\n" + + self.assertEqual(HttpServer.create_option_response(origin), expected_result) + + def test_create_response_with_body(self): + code = 200 + code_message = 'OK' + body = json.dumps({ + 'message': 'все супер! ты молодец' + }) + origin = '127.0.0.1' + + expected_result = 'HTTP/1.1 200 OK\r\n' \ + 'Content-Type: application/json\r\n' \ + f'Content-Length: {len(body.encode())}\r\n' \ + 'Connection: keep-alive\r\n' \ + 'Access-Control-Allow-Origin: 127.0.0.1' \ + '\r\n\r\n' + \ + f'{body}' + + self.assertEqual(HttpServer.create_response(code, code_message, body, origin), expected_result) + + def test_create_response_without_body(self): + code = 200 + code_message = 'OK' + body = '' + origin = '127.0.0.1' + + expected_result = 'HTTP/1.1 200 OK\r\n' \ + 'Content-Type: application/json\r\n' \ + f'Content-Length: {len(body.encode())}\r\n' \ + 'Connection: keep-alive\r\n' \ + 'Access-Control-Allow-Origin: 127.0.0.1' \ + '\r\n\r\n' + + self.assertEqual(HttpServer.create_response(code, code_message, body, origin), expected_result) + + def test_send_data(self): + data = 'test send data' + + expected_result = len(data.encode()) + + socket = MagicMock() + socket.send.side_effect = [expected_result, 0] + + self.assertEqual(expected_result, HttpServer.send_data(socket, data)) + + def test_create_ok_response(self): + data = 'Hello, World!' + origin = '127.0.0.1' + + body = dict() + body['data'] = data + body['code'] = 200 + + body_json = json.dumps(body) + + expected_result = f'HTTP/1.1 200 OK\r\n' \ + f'Content-Type: application/json\r\n' \ + f'Content-Length: {len(body_json.encode())}\r\n' \ + f'Connection: keep-alive\r\n' \ + f'Access-Control-Allow-Origin: {origin}' \ + f'\r\n\r\n{body_json}' + + self.assertEqual(expected_result, HttpServer.create_ok_request_response(data, origin)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..74b76c4 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,28 @@ +import unittest +from threading import Thread + +from main import Main + + +class TestMain(unittest.TestCase): + def test_main_func(self): + main = Main() + main_thread = Thread(target=lambda: main.run()) + + try: + main_thread.start() + except OSError: + assert True + except Exception: + assert True + + try: + main.finish() + except Exception: + assert True + + assert True + + +if __name__ == '__main__': + unittest.main() From fc1905ac5df2666541ca275b89487c9cec229b8e Mon Sep 17 00:00:00 2001 From: iffomko Date: Thu, 1 Jun 2023 17:56:36 +0500 Subject: [PATCH 8/9] =?UTF-8?q?=D0=92=D1=8B=D0=BD=D0=B5=D1=81=20=D0=B1?= =?UTF-8?q?=D0=B8=D0=B7=D0=BD=D0=B5=D1=81=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA?= =?UTF-8?q?=D1=83=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http_server.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/http_server.py b/http_server.py index 499d463..b86fada 100644 --- a/http_server.py +++ b/http_server.py @@ -84,14 +84,23 @@ def start(self): and len(settings.get('get_form')) != 0 ): - matches = re.finditer(settings.get('get_form'), client_data, re.IGNORECASE) - data = [match.start() for match in matches] + data = self._take_data(settings, client_data) response = HttpServer.create_ok_request_response(data, origin) HttpServer.send_data(conn, response) print(f'Client with addr {addr} was disconnected') + @staticmethod + def _take_data(settings: dict, client_data: str): + if 'application/json' in client_data: + matches = re.finditer(settings.get('get_form'), client_data, re.IGNORECASE) + data = [match.start() for match in matches] + + return data + + return client_data + def _create_server(self): self._server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._server.bind((self.__HOST, self.__PORT)) From 04ab22c2084dfad29ec3566e1464bafc7a4a6289 Mon Sep 17 00:00:00 2001 From: iffomko Date: Sat, 3 Jun 2023 16:08:47 +0500 Subject: [PATCH 9/9] =?UTF-8?q?=D0=94=D0=BE=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB?= =?UTF-8?q?=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http_server.py | 24 +++++++++++++------ tests/test_http_client.py | 4 ---- tests/test_http_server.py | 50 +++++++++++++++++++++++++++++++++++---- tests/test_main.py | 26 +++++++++----------- 4 files changed, 74 insertions(+), 30 deletions(-) diff --git a/http_server.py b/http_server.py index b86fada..0d77475 100644 --- a/http_server.py +++ b/http_server.py @@ -19,6 +19,8 @@ def __init__(self): self._server = None + self._working = None + def start(self): """ Запускает сервер @@ -35,7 +37,9 @@ def start(self): self._server.listen() - while True: + self._start_server() + + while self._working: if self._server is None: break @@ -91,15 +95,24 @@ def start(self): print(f'Client with addr {addr} was disconnected') + def _stop_server(self): + self._working = False + + def _start_server(self): + self._working = True + + def stop(self): + self._stop_server() + @staticmethod def _take_data(settings: dict, client_data: str): + data = client_data + if 'application/json' in client_data: matches = re.finditer(settings.get('get_form'), client_data, re.IGNORECASE) data = [match.start() for match in matches] - return data - - return client_data + return data def _create_server(self): self._server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -123,9 +136,6 @@ def send_data(conn: socket, response: str) -> int: while bytes_sent < len(data_to_send): sent = conn.send(data_to_send[bytes_sent:]) - if sent == 0: - break - bytes_sent += sent return bytes_sent diff --git a/tests/test_http_client.py b/tests/test_http_client.py index 6fd7951..cc369fb 100644 --- a/tests/test_http_client.py +++ b/tests/test_http_client.py @@ -187,7 +187,3 @@ def test_connect_with_host_or_port_exc(self): } self.assertEqual(str(ClientMessagesResponse.incorrect_url_or_port.value), HttpClient(settings).get_data()) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_http_server.py b/tests/test_http_server.py index d44c7f2..ec6280a 100644 --- a/tests/test_http_server.py +++ b/tests/test_http_server.py @@ -1,7 +1,10 @@ import json +import re +import socket import unittest from unittest.mock import MagicMock +from enums.server_validating_messages import ServerValidatingMessages from http_server import HttpServer @@ -100,7 +103,7 @@ def test_validate_params_negative_7(self): 'port': 4444, 'cookie': 1234, 'headers': {}, - 'body': '', + 'body': None, 'request': 'GET' } @@ -158,7 +161,7 @@ def test_send_data(self): expected_result = len(data.encode()) socket = MagicMock() - socket.send.side_effect = [expected_result, 0] + socket.send.side_effect = [expected_result] self.assertEqual(expected_result, HttpServer.send_data(socket, data)) @@ -181,6 +184,45 @@ def test_create_ok_response(self): self.assertEqual(expected_result, HttpServer.create_ok_request_response(data, origin)) + def test_create_bad_response(self): + message = 'Hello, World!' + origin = '127.0.0.1' + + body = dict() + body['message'] = message + body['code'] = 400 + + body_json = json.dumps(body) + + expected_result = f'HTTP/1.1 400 BAD REQUEST\r\n' \ + f'Content-Type: application/json\r\n' \ + f'Content-Length: {len(body_json.encode())}\r\n' \ + f'Connection: keep-alive\r\n' \ + f'Access-Control-Allow-Origin: {origin}' \ + f'\r\n\r\n{body_json}' + + self.assertEqual(expected_result, HttpServer.create_bad_request_response(origin, message)) + + def test_take_data(self): + client_data = 'Content-Type: application/json\r\n{"data": "test test test"}' + settings = {'get_form': 'test'} + + matches = re.finditer(settings.get('get_form'), client_data, re.IGNORECASE) + expected_result = [match.start() for match in matches] + + self.assertEqual(expected_result, HttpServer._take_data(settings, client_data)) + + def test_start_working(self): + server = HttpServer() + + server._start_server() + + self.assertEqual(True, server._working) + + def test_stop_working(self): + server = HttpServer() + + server._start_server() + server._stop_server() -if __name__ == '__main__': - unittest.main() + self.assertEqual(False, server._working) diff --git a/tests/test_main.py b/tests/test_main.py index 74b76c4..b561b60 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -4,25 +4,21 @@ from main import Main +def run_and_close_main(main: 'Main'): + main.run() + + class TestMain(unittest.TestCase): def test_main_func(self): main = Main() - main_thread = Thread(target=lambda: main.run()) + main_thread = Thread( + target=run_and_close_main, + args=[main] + ) - try: - main_thread.start() - except OSError: - assert True - except Exception: - assert True + main_thread.daemon = True + main_thread.start() - try: - main.finish() - except Exception: - assert True + main.finish() assert True - - -if __name__ == '__main__': - unittest.main()