From 9155cb17ede54681b6e97fccc219edc253b867ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD=20=D0=A1=D0=BE=D0=BB=D0=BD=D1=86?= =?UTF-8?q?=D0=B5=D0=B2?= Date: Sun, 15 Sep 2024 15:11:51 +0300 Subject: [PATCH] Receive data from client Allow handle client requests with HTTP body for custom functions. - Rewrited "path" function to class; - Simple check clien request of method allowed; - Use "Enumirations" for handler types, HTTP methods; --- config.py | 11 +++-- server/common.py | 19 +++++++++ server/main.py | 95 ++++++++++++++++++++++++++++++-------------- server/urlhandler.py | 51 ++++++++++++++---------- testmod.py | 5 +++ 5 files changed, 128 insertions(+), 53 deletions(-) create mode 100644 server/common.py diff --git a/config.py b/config.py index b9785de..db0fc83 100644 --- a/config.py +++ b/config.py @@ -1,9 +1,11 @@ import os import logging -from server.urlhandler import path +from server.common import HandlerType, HTTPMethod +from server.urlhandler import Path import testmod + SETUP = { "setup": { "log_to_file": False, @@ -14,6 +16,7 @@ SETUP = { } } + if SETUP["setup"]["log_to_file"]: logging.basicConfig(filename=os.path.join(os.getcwd(), "log", "main.log"), filemode="a", encoding="UTF-8", level=SETUP["setup"]["log_level"], @@ -22,7 +25,9 @@ else: logging.basicConfig(encoding="UTF-8", level=SETUP["setup"]["log_level"], format="[%(name)s][%(asctime)s][%(levelname)s] %(message)s") + urls = [ - path("/", "static-file", "index.html"), - path("/func", "function", testmod.work) + Path("/", HandlerType.STATIC_FILE, "index.html"), + Path("/func", HandlerType.FUNCTION, testmod.work, HTTPMethod.GET|HTTPMethod.POST), + Path("/func1", HandlerType.FUNCTION, testmod.work2, HTTPMethod.POST) ] diff --git a/server/common.py b/server/common.py new file mode 100644 index 0000000..dff3e1b --- /dev/null +++ b/server/common.py @@ -0,0 +1,19 @@ +import enum + + +@enum.unique +class HandlerType(enum.Enum): + STATIC_FILE = 0 + REDIRECT = 1 + FUNCTION = 2 + + +class HTTPMethod(enum.Flag): + HEAD = 1 + GET = 2 + POST = 4 + PUT = 8 + DELETE = 16 + OPTIONS = 32 + TRACE = 64 + PATCH = 128 diff --git a/server/main.py b/server/main.py index ff98f1f..ef45812 100644 --- a/server/main.py +++ b/server/main.py @@ -7,6 +7,7 @@ import config from .file_read import fileiter from .response import Response +from .common import HandlerType, HTTPMethod MAX_REQLINE = 64 * 1024 @@ -22,7 +23,7 @@ class HTTPHandler: self.read_fb = conn.makefile("rb") self.write_fb = conn.makefile("wb") - self.http_type = "" + self.http_method = "" self.http_address = "" self.http_get_request = {} self.http_headers = {} @@ -33,7 +34,7 @@ class HTTPHandler: def pre_handle_connection(self): """ - Chain of handle first line in HTTP request + Chain of handle first line of HTTP request """ raw = self.read_fb.readline(MAX_REQLINE + 1) @@ -44,6 +45,7 @@ class HTTPHandler: return req_line = raw.decode().rstrip("\r\n") + # method, address, protocol = req_line_splited req_line_splited = req_line.split(" ") if len(req_line_splited) != 3: @@ -51,27 +53,55 @@ class HTTPHandler: log.debug("Request head too long") else: log.debug("Request head too small") + self.conn.send(b"") self.conn.close() return if (req_line_splited[2] == "HTTP/1.0" or req_line_splited[2] == "HTTP/1.1"): - self.http_type = req_line_splited[0] + + http_method = req_line_splited[0] + else: self.conn.send(b"") self.conn.close() return - if req_line_splited[0] == "GET" and req_line_splited[1].find("?") >= 0: + if http_method == "GET": + self.http_method = HTTPMethod.GET + elif http_method == "POST": + self.http_method = HTTPMethod.POST + elif http_method == "PUT": + self.http_method = HTTPMethod.PUT + elif http_method == "HEAD": + self.http_method = HTTPMethod.HEAD + elif http_method == "DELETE": + self.http_method = HTTPMethod.DELETE + elif http_method == "OPTIONS": + self.http_method = HTTPMethod.OPTIONS + elif http_method == "TRACE": + self.http_method = HTTPMethod.TRACE + elif http_method == "PATCH": + self.http_method = HTTPMethod.PATCH + else: + self.conn.send(b"") + self.conn.close() + return + + if (self.http_method == HTTPMethod.GET and + req_line_splited[1].find("?") >= 0): + addr, get_req = req_line_splited[1].split("?", 1) self.http_address = addr for param in get_req.split("&"): if len(param) == 0: continue + elif param.find("=") >= 0: name, val = param.split("=", 1) + else: name, val = (param, "") @@ -116,22 +146,24 @@ class HTTPHandler: Chain of receive data partitions HTTP request """ - # TODO: Get from headers Content-Length and get body of request, else - # receive 1024 bytes - """ - raw = self.read_fb.readline(1024) + if "Content-Length" in self.http_headers: + # TODO: if content-length biggest of MAX_REQLINE - partition receive + log.debug("Request has contain body, try to read") + log.debug("Check Content-Length value type:") - if len(raw) == 1024: - while True: - self.data += raw + if self.http_headers["Content-Length"].isdigit(): + log.debug(" Pass") + bytes_to_receive = int(self.http_headers["Content-Length"]) + else: + log.error(" Content-Length is not integer") + self.conn.send(b"") + self.conn.close() + return - if len(raw) < 1024: - break - else: - raw = self.read_fb.readline(1024) - """ + log.debug("Want to receive {} bytes from client".format(bytes_to_receive)) + self.data = self.read_fb.read(bytes_to_receive) - log.debug("Type of request: %s" % self.http_type) + log.debug("Request method: %s" % self.http_method) log.debug("Requested address: %s" % self.http_address) log.debug("Request headers: %s" % self.http_headers) log.debug("Request in GET after ?: %s" % self.http_get_request) @@ -145,33 +177,36 @@ class HTTPHandler: """ found = False - for elem in config.urls: - if self.http_address in elem.keys(): - url_metadata = elem[self.http_address] - - if self.http_type in url_metadata[0]: + for path in config.urls: + if self.http_address == path.address: + if self.http_method in path.methods: found = True - if url_metadata[1] == "static-file": - r = Response(data=fileiter(url_metadata[2])) + if path.handler_type == HandlerType.STATIC_FILE: + log.debug("Address associated with static file on path {}".format(path.link)) + r = Response(data=fileiter(path.link)) for i in r: self.write_fb.write(i) self.write_fb.flush() - elif url_metadata[1] == "function": - func = url_metadata[2] + elif path.handler_type == HandlerType.FUNCTION: + log.debug("Address associated with function") + func = path.link if func.__code__.co_argcount == 0: - func_ret = func() + func_return = func() elif func.__code__.co_argcount == 1: - func_ret = func(self.http_headers) + func_return = func(self.http_headers) elif func.__code__.co_argcount == 2: - func_ret = func(self.http_headers, self.http_get_request) + func_return = func(self.http_headers, self.http_get_request) - for i in func_ret: + elif func.__code__.co_argcount == 3: + func_return = func(self.http_headers, self.http_get_request, self.data) + + for i in func_return: self.write_fb.write(i) self.write_fb.flush() diff --git a/server/urlhandler.py b/server/urlhandler.py index a1edb82..9317a4f 100644 --- a/server/urlhandler.py +++ b/server/urlhandler.py @@ -1,29 +1,40 @@ -import os import logging +import os + +from .common import HandlerType, HTTPMethod log = logging.getLogger(__name__) -def path(path="/", _type="static-file", link="index.html", methods=("GET",)): - log.debug("Start generate path for %s address" % path) +class Path: + def __init__(self, address, handler_type=HandlerType.STATIC_FILE, link="index.html", methods=HTTPMethod.GET): + self.address = address + self.handler_type = handler_type + self.link = link + self.methods = methods - if _type == "static-file": - log.info("Path %s is %s and linked %s for methods %s" % (path, _type, link, methods)) - return { - path: (methods, _type, os.path.join(os.getcwd(), link)) - } + def check_method(self, method): + return method in self.methods - elif _type == "redirect": - log.info("Path %s is %s and linked %s for methods %s" % (path, _type, link, methods)) - return { - path: (methods, _type, link) - } + @property + def handler_type(self): + return self._handler_type - elif _type == "function": - log.info("Path %s is %s and linked %s for methods %s" % (path, _type, link, methods)) - return { - path: (methods, _type, link) - } - else: - log.error("Path %s is %s but it type is unknown" % (path, _type)) + @handler_type.setter + def handler_type(self, value): + if not isinstance(value, HandlerType): + raise ValueError + + self._handler_type = value + + @property + def methods(self): + return self._methods + + @methods.setter + def methods(self, value): + if not isinstance(value, HTTPMethod): + raise ValueError + + self._methods = value diff --git a/testmod.py b/testmod.py index 6790e38..0eb4880 100644 --- a/testmod.py +++ b/testmod.py @@ -5,3 +5,8 @@ def work(headers, request): print(headers, request) return Response(data=b"Tested!") + +def work2(headers, get_request, post_request): + print(headers, get_request, post_request) + + return Response(data=b"Pass")