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:
parent
887e8da01b
commit
f7d2a675fb
6 changed files with 159 additions and 67 deletions
11
config.py
11
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 = {
|
||||||
|
"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)
|
||||||
]
|
]
|
||||||
|
|
152
server/main.py
152
server/main.py
|
@ -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
43
server/response.py
Normal 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
|
|
@ -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
7
server/urlhandler.py
Normal 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
7
testmod.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from server.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
def work(headers, request):
|
||||||
|
print(headers, request)
|
||||||
|
|
||||||
|
return Response(data=b"Tested!")
|
Loading…
Reference in a new issue