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;
This commit is contained in:
Иван Солнцев 2024-09-15 15:11:51 +03:00
parent c7413887cc
commit 9155cb17ed
5 changed files with 128 additions and 53 deletions

View file

@ -1,9 +1,11 @@
import os import os
import logging import logging
from server.urlhandler import path from server.common import HandlerType, HTTPMethod
from server.urlhandler import Path
import testmod import testmod
SETUP = { SETUP = {
"setup": { "setup": {
"log_to_file": False, "log_to_file": False,
@ -14,6 +16,7 @@ SETUP = {
} }
} }
if SETUP["setup"]["log_to_file"]: if SETUP["setup"]["log_to_file"]:
logging.basicConfig(filename=os.path.join(os.getcwd(), "log", "main.log"), filemode="a", encoding="UTF-8", logging.basicConfig(filename=os.path.join(os.getcwd(), "log", "main.log"), filemode="a", encoding="UTF-8",
level=SETUP["setup"]["log_level"], level=SETUP["setup"]["log_level"],
@ -22,7 +25,9 @@ else:
logging.basicConfig(encoding="UTF-8", level=SETUP["setup"]["log_level"], logging.basicConfig(encoding="UTF-8", level=SETUP["setup"]["log_level"],
format="[%(name)s][%(asctime)s][%(levelname)s] %(message)s") format="[%(name)s][%(asctime)s][%(levelname)s] %(message)s")
urls = [ urls = [
path("/", "static-file", "index.html"), Path("/", HandlerType.STATIC_FILE, "index.html"),
path("/func", "function", testmod.work) Path("/func", HandlerType.FUNCTION, testmod.work, HTTPMethod.GET|HTTPMethod.POST),
Path("/func1", HandlerType.FUNCTION, testmod.work2, HTTPMethod.POST)
] ]

19
server/common.py Normal file
View file

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

View file

@ -7,6 +7,7 @@ import config
from .file_read import fileiter from .file_read import fileiter
from .response import Response from .response import Response
from .common import HandlerType, HTTPMethod
MAX_REQLINE = 64 * 1024 MAX_REQLINE = 64 * 1024
@ -22,7 +23,7 @@ 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_method = ""
self.http_address = "" self.http_address = ""
self.http_get_request = {} self.http_get_request = {}
self.http_headers = {} self.http_headers = {}
@ -33,7 +34,7 @@ class HTTPHandler:
def pre_handle_connection(self): 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) raw = self.read_fb.readline(MAX_REQLINE + 1)
@ -44,6 +45,7 @@ class HTTPHandler:
return return
req_line = raw.decode().rstrip("\r\n") req_line = raw.decode().rstrip("\r\n")
# method, address, protocol = req_line_splited
req_line_splited = req_line.split(" ") req_line_splited = req_line.split(" ")
if len(req_line_splited) != 3: if len(req_line_splited) != 3:
@ -51,27 +53,55 @@ class HTTPHandler:
log.debug("Request head too long") log.debug("Request head too long")
else: else:
log.debug("Request head too small") log.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 if (req_line_splited[2] == "HTTP/1.0" or
req_line_splited[2] == "HTTP/1.1"): req_line_splited[2] == "HTTP/1.1"):
self.http_type = req_line_splited[0]
http_method = req_line_splited[0]
else: else:
self.conn.send(b"") self.conn.send(b"")
self.conn.close() self.conn.close()
return 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) addr, get_req = req_line_splited[1].split("?", 1)
self.http_address = addr self.http_address = addr
for param in get_req.split("&"): for param in get_req.split("&"):
if len(param) == 0: if len(param) == 0:
continue continue
elif param.find("=") >= 0: elif param.find("=") >= 0:
name, val = param.split("=", 1) name, val = param.split("=", 1)
else: else:
name, val = (param, "") name, val = (param, "")
@ -116,22 +146,24 @@ class HTTPHandler:
Chain of receive data partitions HTTP request Chain of receive data partitions HTTP request
""" """
# TODO: Get from headers Content-Length and get body of request, else if "Content-Length" in self.http_headers:
# receive 1024 bytes # TODO: if content-length biggest of MAX_REQLINE - partition receive
""" log.debug("Request has contain body, try to read")
raw = self.read_fb.readline(1024) log.debug("Check Content-Length value type:")
if len(raw) == 1024: if self.http_headers["Content-Length"].isdigit():
while True: log.debug(" Pass")
self.data += raw bytes_to_receive = int(self.http_headers["Content-Length"])
if len(raw) < 1024:
break
else: else:
raw = self.read_fb.readline(1024) log.error(" Content-Length is not integer")
""" self.conn.send(b"")
self.conn.close()
return
log.debug("Type of request: %s" % self.http_type) log.debug("Want to receive {} bytes from client".format(bytes_to_receive))
self.data = self.read_fb.read(bytes_to_receive)
log.debug("Request method: %s" % self.http_method)
log.debug("Requested address: %s" % self.http_address) log.debug("Requested address: %s" % self.http_address)
log.debug("Request headers: %s" % self.http_headers) log.debug("Request headers: %s" % self.http_headers)
log.debug("Request in GET after ?: %s" % self.http_get_request) log.debug("Request in GET after ?: %s" % self.http_get_request)
@ -145,33 +177,36 @@ class HTTPHandler:
""" """
found = False found = False
for elem in config.urls: for path in config.urls:
if self.http_address in elem.keys(): if self.http_address == path.address:
url_metadata = elem[self.http_address] if self.http_method in path.methods:
if self.http_type in url_metadata[0]:
found = True found = True
if url_metadata[1] == "static-file": if path.handler_type == HandlerType.STATIC_FILE:
r = Response(data=fileiter(url_metadata[2])) log.debug("Address associated with static file on path {}".format(path.link))
r = Response(data=fileiter(path.link))
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": elif path.handler_type == HandlerType.FUNCTION:
func = url_metadata[2] log.debug("Address associated with function")
func = path.link
if func.__code__.co_argcount == 0: if func.__code__.co_argcount == 0:
func_ret = func() func_return = func()
elif func.__code__.co_argcount == 1: elif func.__code__.co_argcount == 1:
func_ret = func(self.http_headers) func_return = func(self.http_headers)
elif func.__code__.co_argcount == 2: 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.write(i)
self.write_fb.flush() self.write_fb.flush()

View file

@ -1,29 +1,40 @@
import os
import logging import logging
import os
from .common import HandlerType, HTTPMethod
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def path(path="/", _type="static-file", link="index.html", methods=("GET",)): class Path:
log.debug("Start generate path for %s address" % 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": def check_method(self, method):
log.info("Path %s is %s and linked %s for methods %s" % (path, _type, link, methods)) return method in self.methods
return {
path: (methods, _type, os.path.join(os.getcwd(), link))
}
elif _type == "redirect": @property
log.info("Path %s is %s and linked %s for methods %s" % (path, _type, link, methods)) def handler_type(self):
return { return self._handler_type
path: (methods, _type, link)
}
elif _type == "function": @handler_type.setter
log.info("Path %s is %s and linked %s for methods %s" % (path, _type, link, methods)) def handler_type(self, value):
return { if not isinstance(value, HandlerType):
path: (methods, _type, link) raise ValueError
}
else: self._handler_type = value
log.error("Path %s is %s but it type is unknown" % (path, _type))
@property
def methods(self):
return self._methods
@methods.setter
def methods(self, value):
if not isinstance(value, HTTPMethod):
raise ValueError
self._methods = value

View file

@ -5,3 +5,8 @@ def work(headers, request):
print(headers, request) print(headers, request)
return Response(data=b"Tested!") return Response(data=b"Tested!")
def work2(headers, get_request, post_request):
print(headers, get_request, post_request)
return Response(data=b"Pass")