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)
This commit is contained in:
Иван Солнцев 2024-02-26 02:21:00 +03:00
parent 887e8da01b
commit f7d2a675fb
6 changed files with 159 additions and 67 deletions

View file

@ -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 = {
"setup": {
"log_level": DEBUG
},
"server": { "server": {
"port": 8080, "port": 8080,
} }
@ -9,5 +16,5 @@ SETUP = {
urls = [ urls = [
path("/", "static-file", "index.html"), path("/", "static-file", "index.html"),
path("/template", "template", "index.html") path("/func", "function", testmod.work)
] ]

View file

@ -5,61 +5,13 @@ import logging
import config import config
from .file_read import fileiter from .file_read import fileiter
from .response import Response
MAX_REQLINE = 64 * 1024 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: class HTTPHandler:
http_type = ""
http_address = ""
headers = {}
data = b""
def __init__(self, conn, addr): def __init__(self, conn, addr):
self.conn = conn self.conn = conn
self.addr = addr self.addr = addr
@ -67,9 +19,19 @@ class HTTPHandler:
self.read_fb = conn.makefile("rb") self.read_fb = conn.makefile("rb")
self.write_fb = conn.makefile("wb") 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() self.pre_handle_connection()
def pre_handle_connection(self): def pre_handle_connection(self):
"""
Chain of handle first line in HTTP request
"""
raw = self.read_fb.readline(MAX_REQLINE + 1) raw = self.read_fb.readline(MAX_REQLINE + 1)
if len(raw) > MAX_REQLINE: if len(raw) > MAX_REQLINE:
@ -82,20 +44,44 @@ class HTTPHandler:
req_line_splited = req_line.split(" ") req_line_splited = req_line.split(" ")
if len(req_line_splited) != 3: 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.send(b"")
self.conn.close() self.conn.close()
return return
if req_line_splited[2] == "HTTP/1.0" or req_line_splited[2] == "HTTP/1.1": 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_type = req_line_splited[0]
self.http_address = req_line_splited[1]
self.http_header_handle()
else: else:
self.conn.send(b"") self.conn.send(b"")
self.conn.close() 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): def http_header_handle(self):
"""
Chain of handle headers in HTTP request
"""
while True: while True:
raw = self.read_fb.readline(MAX_REQLINE + 1) raw = self.read_fb.readline(MAX_REQLINE + 1)
@ -116,12 +102,15 @@ class HTTPHandler:
else: else:
decoded_data = raw.decode("UTF-8").rstrip("\r\n") decoded_data = raw.decode("UTF-8").rstrip("\r\n")
decoded_data_split = decoded_data.split(":", 1) 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): 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) raw = self.read_fb.readline(1024)
if len(raw) == 1024: if len(raw) == 1024:
@ -136,12 +125,16 @@ class HTTPHandler:
logging.debug(self.http_type) logging.debug(self.http_type)
logging.debug(self.http_address) logging.debug(self.http_address)
logging.debug(self.headers) logging.debug(self.http_headers)
logging.debug(self.http_get_request)
logging.debug(self.data) logging.debug(self.data)
self.send_form_data() self.send_form_data()
def send_form_data(self): def send_form_data(self):
"""
Chain of handle, pack & send data to client
"""
found = False found = False
for elem in config.urls: for elem in config.urls:
@ -157,9 +150,26 @@ class HTTPHandler:
for i in r: for i in r:
self.write_fb.write(i) self.write_fb.write(i)
self.write_fb.flush() 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: else:
logging.warning("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 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: for i in r:
self.write_fb.write(i) self.write_fb.write(i)
@ -179,9 +189,32 @@ class HTTPHandler:
def start(): def start():
logging.basicConfig(filename="main.log", filemode="a", encoding="UTF-8", 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: try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as main_server_socket: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as main_server_socket:
main_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) main_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
@ -197,3 +230,4 @@ def start():
except KeyboardInterrupt: except KeyboardInterrupt:
logging.info("Server get keyboard interrupt. Initialize server to stop") logging.info("Server get keyboard interrupt. Initialize server to stop")
logging.info("==================== End of log ====================")

43
server/response.py Normal file
View file

@ -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

View file

@ -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))}

7
server/urlhandler.py Normal file
View file

@ -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)}

7
testmod.py Normal file
View file

@ -0,0 +1,7 @@
from server.response import Response
def work(headers, request):
print(headers, request)
return Response(data=b"Tested!")