summaryrefslogtreecommitdiff
path: root/day9/task5_vue/backend
diff options
context:
space:
mode:
Diffstat (limited to 'day9/task5_vue/backend')
-rw-r--r--day9/task5_vue/backend/core.py82
-rw-r--r--day9/task5_vue/backend/database.py43
-rw-r--r--day9/task5_vue/backend/router.py41
-rw-r--r--day9/task5_vue/backend/server.py73
-rw-r--r--day9/task5_vue/backend/utils.py131
5 files changed, 370 insertions, 0 deletions
diff --git a/day9/task5_vue/backend/core.py b/day9/task5_vue/backend/core.py
new file mode 100644
index 0000000..71c6d5a
--- /dev/null
+++ b/day9/task5_vue/backend/core.py
@@ -0,0 +1,82 @@
+from abc import ABC, abstractmethod
+import json
+
+from backend.utils import HTTP_STATUS_CODES
+
+
+class Response(ABC):
+ @property
+ def status_code(self) -> int:
+ """
+ По дефолту возвращается статус 200
+ """
+ return 200
+
+ @property
+ @abstractmethod
+ def content_type(self) -> str:
+ pass
+
+ @property
+ @abstractmethod
+ def content(self) -> bytes:
+ pass
+
+
+class HtmlResponse(Response):
+ def __init__(self, html, status_code=200):
+ self._html: str = html
+ self._code = status_code
+
+ @property
+ def status_code(self) -> int:
+ return self._code
+
+ @property
+ def content_type(self) -> str:
+ return 'text/html'
+
+ @property
+ def content(self) -> bytes:
+ return self._html.encode()
+
+
+class TextFileResponse(Response):
+ def __init__(self, path, extension):
+ with open(path, 'rb') as f:
+ self._content = f.read()
+
+ self._extension = extension
+
+ @property
+ def content_type(self) -> str:
+ return f'text/{self._extension}'
+
+ @property
+ def content(self) -> bytes:
+ return self._content
+
+
+class ImageResponse(TextFileResponse):
+ @property
+ def content_type(self) -> str:
+ return f'image/{self._extension}'
+
+
+class JsonResponse(Response):
+ def __init__(self, json_object):
+ self._json_str = json.dumps(json_object, ensure_ascii=False)
+
+ @property
+ def content_type(self) -> str:
+ return 'application/json'
+
+ @property
+ def content(self) -> bytes:
+ return self._json_str.encode()
+
+
+class ErrorResponse(HtmlResponse):
+ def __init__(self, http_code, message=''):
+ html = f'<center><h1>ERROR {http_code} {HTTP_STATUS_CODES[http_code].upper()}</h1></center><br>{message}'
+ super().__init__(html, http_code)
diff --git a/day9/task5_vue/backend/database.py b/day9/task5_vue/backend/database.py
new file mode 100644
index 0000000..5484041
--- /dev/null
+++ b/day9/task5_vue/backend/database.py
@@ -0,0 +1,43 @@
+import MySQLdb
+from backend.config import *
+
+import logging
+
+
+def db_column_names():
+ cursor = db.cursor()
+ cursor.execute('DESCRIBE table_task1;')
+ table_structure = cursor.fetchall()
+ table_headers = [field[0] for field in table_structure]
+ return table_headers
+
+
+logger = logging.getLogger('tableApp')
+logger.info(f'Trying to connect to database "{DATABASE_NAME}@{HOST}"...')
+# В файле config.py создайте соответствующие переменные
+db = MySQLdb.connect(
+ host=HOST,
+ user=USERNAME,
+ passwd=PASSWORD,
+ db=DATABASE_NAME
+)
+logger.info('Connected')
+
+logger.info(f'Preparing table "table_task1"...')
+db.cursor().execute(
+ '''
+ CREATE TABLE IF NOT EXISTS `table_task1` (
+ `service_id` int(11) NOT NULL AUTO_INCREMENT,
+ `servtype` varchar(20) NOT NULL DEFAULT 'hosting',
+ `subtype` varchar(32) NOT NULL DEFAULT '',
+ `user_id` bigint(20) NOT NULL,
+ `referrer_user_id` bigint(20) NOT NULL,
+ `state` varchar(1) NOT NULL DEFAULT 'N',
+ `creation_date` date NOT NULL DEFAULT '0000-01-01',
+ `creation_time` time NOT NULL DEFAULT '00:00:00',
+ `creation_request_sent_date` datetime DEFAULT NULL,
+ `notified_about_expiration` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`service_id`)
+ ) ENGINE=InnoDB AUTO_INCREMENT=35109400 DEFAULT CHARSET=utf8;
+ '''
+)
diff --git a/day9/task5_vue/backend/router.py b/day9/task5_vue/backend/router.py
new file mode 100644
index 0000000..be8305e
--- /dev/null
+++ b/day9/task5_vue/backend/router.py
@@ -0,0 +1,41 @@
+import re
+from backend.utils import NOT_FOUND_CODE, BAD_REQUEST_CODE
+from backend.core import Response, ErrorResponse
+
+
+def route(url_format, methods=None):
+ 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 ErrorResponse(BAD_REQUEST_CODE)
+
+ return func(query, *match.groups(), *args, **kwargs)
+
+ _router_tree[url_format] = _router_tree.get(url_format, {})
+ for method in methods:
+ _router_tree[url_format][method] = inner
+
+ return inner
+
+ return wrapper
+
+
+def run(request) -> Response:
+ res = ErrorResponse(NOT_FOUND_CODE)
+
+ method, url = request['method'], request['url']
+ for url_pattern in _router_tree:
+ if re.fullmatch(url_pattern, url) and method in _router_tree[url_pattern]:
+ res = _router_tree[url_pattern][method](url, request['query'])
+ break
+
+ return res
+
+
+_router_tree = {}
diff --git a/day9/task5_vue/backend/server.py b/day9/task5_vue/backend/server.py
new file mode 100644
index 0000000..48d5946
--- /dev/null
+++ b/day9/task5_vue/backend/server.py
@@ -0,0 +1,73 @@
+from http.server import HTTPServer, BaseHTTPRequestHandler
+from urllib.parse import parse_qs
+
+from backend.router import run
+from backend.utils import parse_multipart_form
+
+import logging
+import json
+
+
+class MyHTTPRequestHandler(BaseHTTPRequestHandler):
+ def _set_response(self, code, content_type):
+ self.send_response(code)
+ self.send_header('Content-type', content_type)
+ self.end_headers()
+
+ def do_GET(self):
+ try:
+ content_length = int(self.headers['Content-Length'])
+ get_data = parse_qs(self.rfile.read(content_length).decode('utf-8'))
+ except TypeError:
+ get_data = {}
+
+ for key in get_data:
+ get_data[key] = get_data[key][0]
+
+ self.finalize_request(run({
+ 'url': self.path,
+ 'method': 'GET',
+ 'query': get_data
+ }))
+
+ def do_POST(self):
+ content_length = int(self.headers['Content-Length'])
+ content_type = self.headers['Content-type']
+ post_data = self.rfile.read(content_length)
+
+ if content_type.split(';')[0] == 'multipart/form-data':
+ files = parse_multipart_form(post_data)
+ self.finalize_request(run({
+ 'url': self.path,
+ 'method': 'POST',
+ 'query': {'files': files}
+ }))
+
+ elif content_type.split(';')[0] in ['text/plain', 'application/x-www-form-urlencoded']:
+ post_data = parse_qs(post_data.decode('utf-8'))
+ for key in post_data:
+ post_data[key] = post_data[key][0]
+
+ self.finalize_request(run({
+ 'url': self.path,
+ 'method': 'POST',
+ 'query': post_data
+ }))
+
+ elif content_type.split(';')[0] == 'application/json':
+ self.finalize_request(run({
+ 'url': self.path,
+ 'method': 'POST',
+ 'query': json.loads(post_data.decode('utf-8'))
+ }))
+
+ def finalize_request(self, response):
+ self._set_response(response.status_code, response.content_type)
+ self.wfile.write(response.content)
+
+
+def start_server(host, port):
+ server_address = (host, port)
+ httpd = HTTPServer(server_address, MyHTTPRequestHandler)
+ logging.getLogger('tableApp').info(f'Server started on {host}:{port}')
+ httpd.serve_forever()
diff --git a/day9/task5_vue/backend/utils.py b/day9/task5_vue/backend/utils.py
new file mode 100644
index 0000000..c1b3a04
--- /dev/null
+++ b/day9/task5_vue/backend/utils.py
@@ -0,0 +1,131 @@
+from io import BytesIO
+
+
+CHUNK_SIZE = 49600
+
+HTTP_STATUS_CODES = {
+ 100: "Continue",
+ 101: "Switching Protocols",
+ 102: "Processing",
+ 200: "OK",
+ 201: "Created",
+ 202: "Accepted",
+ 203: "Non Authoritative Information",
+ 204: "No Content",
+ 205: "Reset Content",
+ 206: "Partial Content",
+ 207: "Multi Status",
+ 226: "IM Used", # see RFC 3229
+ 300: "Multiple Choices",
+ 301: "Moved Permanently",
+ 302: "Found",
+ 303: "See Other",
+ 304: "Not Modified",
+ 305: "Use Proxy",
+ 307: "Temporary Redirect",
+ 308: "Permanent Redirect",
+ 400: "Bad Request",
+ 401: "Unauthorized",
+ 402: "Payment Required", # unused
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 406: "Not Acceptable",
+ 407: "Proxy Authentication Required",
+ 408: "Request Timeout",
+ 409: "Conflict",
+ 410: "Gone",
+ 411: "Length Required",
+ 412: "Precondition Failed",
+ 413: "Request Entity Too Large",
+ 414: "Request URI Too Long",
+ 415: "Unsupported Media Type",
+ 416: "Requested Range Not Satisfiable",
+ 417: "Expectation Failed",
+ 418: "I'm a teapot", # see RFC 2324
+ 421: "Misdirected Request", # see RFC 7540
+ 422: "Unprocessable Entity",
+ 423: "Locked",
+ 424: "Failed Dependency",
+ 426: "Upgrade Required",
+ 428: "Precondition Required", # see RFC 6585
+ 429: "Too Many Requests",
+ 431: "Request Header Fields Too Large",
+ 449: "Retry With", # proprietary MS extension
+ 451: "Unavailable For Legal Reasons",
+ 500: "Internal Server Error",
+ 501: "Not Implemented",
+ 502: "Bad Gateway",
+ 503: "Service Unavailable",
+ 504: "Gateway Timeout",
+ 505: "HTTP Version Not Supported",
+ 507: "Insufficient Storage",
+ 510: "Not Extended",
+}
+
+SUCCESS_CODE = 200
+BAD_REQUEST_CODE = 400
+NOT_FOUND_CODE = 404
+METHOD_NOT_ALLOWED_CODE = 405
+
+
+def render_template(path, **kwargs):
+ with open(path, encoding='utf-8') as f:
+ template = f.read()
+
+ for key in kwargs:
+ template = template.replace(f'%%{key}%%', kwargs[key])
+
+ return template
+
+
+def parse_multipart_form(data: bytes):
+ b = BytesIO(data)
+ separator = b.readline().strip()
+
+ files = []
+
+ while True:
+ line = b.readline().strip().strip(b'-')
+ if not line:
+ break
+
+ headers_raw = []
+ while not line.strip() == b'':
+ headers_raw.append(line.strip().decode())
+ line = b.readline()
+
+ headers = {'Content-Type': headers_raw[1].split(': ')[1]}
+ for pair in headers_raw[0].split(': ')[1].split('; ')[1:]:
+ key, value = pair.split('=')
+ headers[key] = value[1:-1]
+
+ data = b''
+ prev_chunk = b.read(CHUNK_SIZE)
+ chunk = b.read(CHUNK_SIZE)
+ while separator not in (prev_chunk + chunk):
+ data += prev_chunk
+ prev_chunk = chunk
+ chunk = b.read(CHUNK_SIZE)
+
+ if not chunk:
+ break
+
+ chunk = (prev_chunk + chunk)
+ if chunk.startswith(separator) or chunk.endswith(separator):
+ with_sep = chunk.strip(separator)
+ else:
+ if separator in chunk:
+ with_sep, buffer = chunk.split(separator)
+ b = BytesIO(buffer + b.read())
+ b.readline()
+
+ else:
+ with_sep = chunk
+
+ data += with_sep
+ headers['data'] = data
+
+ files.append(headers)
+
+ return files