From f7d2a675fb91e4c4b0042ac6be980d423db2863b Mon Sep 17 00:00:00 2001 From: John Solntsev Date: Mon, 26 Feb 2024 02:21:00 +0300 Subject: [PATCH] Add handle & send data of user defined functions Add - handle GET request - handle & send data of user defined functions - project logotype to log when start server :) Change - move set log level to config file - move Response class to file Fix - log message of small or long first request line Template - for URL redirection (http_addr -> new_http_addr) --- config.py | 11 +++- server/main.py | 152 ++++++++++++++++++++++++++----------------- server/response.py | 43 ++++++++++++ server/url.py | 6 -- server/urlhandler.py | 7 ++ testmod.py | 7 ++ 6 files changed, 159 insertions(+), 67 deletions(-) create mode 100644 server/response.py delete mode 100644 server/url.py create mode 100644 server/urlhandler.py create mode 100644 testmod.py diff --git a/config.py b/config.py index 874c35c..1dcacd5 100644 --- a/config.py +++ b/config.py @@ -1,7 +1,14 @@ -from server.url import path +from logging import NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL + +import testmod + +from server.urlhandler import path SETUP = { + "setup": { + "log_level": DEBUG + }, "server": { "port": 8080, } @@ -9,5 +16,5 @@ SETUP = { urls = [ path("/", "static-file", "index.html"), - path("/template", "template", "index.html") + path("/func", "function", testmod.work) ] diff --git a/server/main.py b/server/main.py index b7eaf46..b73909f 100644 --- a/server/main.py +++ b/server/main.py @@ -5,61 +5,13 @@ import logging import config from .file_read import fileiter +from .response import Response MAX_REQLINE = 64 * 1024 -STATUS_CODE = { - 200: "OK", - 404: "Not Found", - 500: "Internal Server Error", -} - - -class Response: - def __iter__(self): - return self - - def __init__(self, status_code=200, additional_headers={}, data=None): - self.status_code = status_code - self.additional_headers = additional_headers - self.data = data - self.first = True - - def __next__(self): - if self.first: - resp = "HTTP/1.1 {} {}\r\n" - resp += "Server: cnserv\r\n" - resp += "Connection: close\r\n" - for key, val in self.additional_headers.items(): - resp += "{}: {}\r\n".format(key, val) - resp += "\r\n" - - resp = resp.format(self.status_code, STATUS_CODE[self.status_code]) - resp = resp.encode("UTF-8") - - if type(self.data) == bytes: - resp += self.data - - self.first = False - return resp - - else: - if type(self.data) == bytes: - raise StopIteration - - elif hasattr(self.data, "__iter__"): - try: - return next(self.data) - except StopIteration: - raise StopIteration class HTTPHandler: - http_type = "" - http_address = "" - headers = {} - data = b"" - def __init__(self, conn, addr): self.conn = conn self.addr = addr @@ -67,9 +19,19 @@ class HTTPHandler: self.read_fb = conn.makefile("rb") self.write_fb = conn.makefile("wb") + self.http_type = "" + self.http_address = "" + self.http_get_request = {} + self.http_headers = {} + + self.data = b"" + self.pre_handle_connection() def pre_handle_connection(self): + """ + Chain of handle first line in HTTP request + """ raw = self.read_fb.readline(MAX_REQLINE + 1) if len(raw) > MAX_REQLINE: @@ -82,20 +44,44 @@ class HTTPHandler: req_line_splited = req_line.split(" ") if len(req_line_splited) != 3: - logging.debug("Request head too long") + if len(req_line_splited) > 3: + logging.debug("Request head too long") + else: + logging.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] - self.http_address = req_line_splited[1] - self.http_header_handle() else: self.conn.send(b"") self.conn.close() + return + + if req_line_splited[0] == "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, "") + + self.http_get_request.update({name: val}) + else: + self.http_address = req_line_splited[1] + self.http_get_request = {} + + self.http_header_handle() def http_header_handle(self): + """ + Chain of handle headers in HTTP request + """ while True: raw = self.read_fb.readline(MAX_REQLINE + 1) @@ -116,12 +102,15 @@ class HTTPHandler: else: decoded_data = raw.decode("UTF-8").rstrip("\r\n") decoded_data_split = decoded_data.split(":", 1) - self.headers.update({decoded_data_split[0]: decoded_data_split[1].strip(" ")}) + self.http_headers.update({decoded_data_split[0]: decoded_data_split[1].strip(" ")}) def http_data_handle(self): - # TODO: Get from headers Content-Length and get body from request + """ + 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 len(raw) == 1024: @@ -136,12 +125,16 @@ class HTTPHandler: logging.debug(self.http_type) logging.debug(self.http_address) - logging.debug(self.headers) + logging.debug(self.http_headers) + logging.debug(self.http_get_request) logging.debug(self.data) self.send_form_data() def send_form_data(self): + """ + Chain of handle, pack & send data to client + """ found = False for elem in config.urls: @@ -157,9 +150,26 @@ class HTTPHandler: for i in r: self.write_fb.write(i) self.write_fb.flush() + + elif url_metadata[1] == "function": + func = url_metadata[2] + + if func.__code__.co_argcount == 0: + func_ret = func() + + elif func.__code__.co_argcount == 1: + func_ret = func(self.http_headers) + + elif func.__code__.co_argcount == 2: + func_ret = func(self.http_headers, self.http_get_request) + + for i in func_ret: + self.write_fb.write(i) + self.write_fb.flush() + else: - logging.warning("Address configured on server, but not allowed URL type in URLs list") - r = Response(status_code=404, data=b"Address configured on server, but not allowed URL type in URLs list") + logging.warning("Address configured on server, but type not allowed URL type in URLs list") + r = Response(status_code=404, data=b"Address configured on server, but type not allowed URL type in URLs list") for i in r: self.write_fb.write(i) @@ -179,9 +189,32 @@ class HTTPHandler: def start(): logging.basicConfig(filename="main.log", filemode="a", encoding="UTF-8", - level=logging.DEBUG, format="[%(asctime)s][%(levelname)s] %(message)s") + level=config.SETUP["setup"]["log_level"], format="[%(asctime)s][%(levelname)s] %(message)s") + + project_logotype = """ + ___________ _____ _____________ + / _______ / _/ /_ /_____ _____/ + / / /_/ _/ __ /_ / / + / / / _/ /_ / / / + / / / _/____/_ / / / + / / __ / _______ / / / + / /_______/ / / / / / / / +/___________/ /_/ /_/ /_/ + ___ __ ___________ _____________ + / _ | / / / _________/ /_____ _____/ + / / || / / / / / / + / / || / / / /___ / / + / / || / / / ____/ / / + / / || / / / / / / + / / ||/ / / /_________ / / + /_/ |__/ /___________/ /_/ + +==================================================== +=== Server start separator === +====================================================""" + + logging.info(project_logotype) - logging.info("=== Separator ===") try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as main_server_socket: main_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) @@ -197,3 +230,4 @@ def start(): except KeyboardInterrupt: logging.info("Server get keyboard interrupt. Initialize server to stop") + logging.info("==================== End of log ====================") diff --git a/server/response.py b/server/response.py new file mode 100644 index 0000000..dd8124d --- /dev/null +++ b/server/response.py @@ -0,0 +1,43 @@ +STATUS_CODE = { + 200: "OK", + 404: "Not Found", + 500: "Internal Server Error", +} + +class Response: + def __iter__(self): + return self + + def __init__(self, status_code=200, additional_headers={}, data=None): + self.status_code = status_code + self.additional_headers = additional_headers + self.data = data + self.first = True + + def __next__(self): + if self.first: + resp = "HTTP/1.1 {} {}\r\n" + resp += "Server: cnserv\r\n" + resp += "Connection: close\r\n" + for key, val in self.additional_headers.items(): + resp += "{}: {}\r\n".format(key, val) + resp += "\r\n" + + resp = resp.format(self.status_code, STATUS_CODE[self.status_code]) + resp = resp.encode("UTF-8") + + if type(self.data) == bytes: + resp += self.data + + self.first = False + return resp + + else: + if type(self.data) == bytes: + raise StopIteration + + elif hasattr(self.data, "__iter__"): + try: + return next(self.data) + except StopIteration: + raise StopIteration diff --git a/server/url.py b/server/url.py deleted file mode 100644 index a139b6c..0000000 --- a/server/url.py +++ /dev/null @@ -1,6 +0,0 @@ -def path(path="/", _type="static-file", _file="index.html", methods=("GET",)): - if _type == "static-file": - return {path: (methods, _type, "static/{}".format(_file))} - - elif _type == "template": - return {path: (methods, _type, "template/{}".format(_file))} diff --git a/server/urlhandler.py b/server/urlhandler.py new file mode 100644 index 0000000..5d5f28f --- /dev/null +++ b/server/urlhandler.py @@ -0,0 +1,7 @@ +def path(path="/", _type="static-file", link="index.html", methods=("GET",)): + if _type == "static-file": + return {path: (methods, _type, "static/{}".format(link))} + if _type == "redirect": + return {path: (methods, _type, link)} + elif _type == "function": + return {path: (methods, _type, link)} diff --git a/testmod.py b/testmod.py new file mode 100644 index 0000000..6790e38 --- /dev/null +++ b/testmod.py @@ -0,0 +1,7 @@ +from server.response import Response + + +def work(headers, request): + print(headers, request) + + return Response(data=b"Tested!")