Иван Солнцев
887e8da01b
Add - internal project file iterator function - path function to locate user files (first allocate as static-files) - primitive HTML page files for demonstrate server functional Change - Response class as iterator - code for send response to client
199 lines
5.8 KiB
Python
199 lines
5.8 KiB
Python
import threading
|
|
import socket
|
|
import logging
|
|
|
|
import config
|
|
|
|
from .file_read import fileiter
|
|
|
|
|
|
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
|
|
|
|
self.read_fb = conn.makefile("rb")
|
|
self.write_fb = conn.makefile("wb")
|
|
|
|
self.pre_handle_connection()
|
|
|
|
def pre_handle_connection(self):
|
|
raw = self.read_fb.readline(MAX_REQLINE + 1)
|
|
|
|
if len(raw) > MAX_REQLINE:
|
|
logging.debug("Request line too long")
|
|
self.conn.send(b"")
|
|
self.conn.close()
|
|
return
|
|
|
|
req_line = raw.decode().rstrip("\r\n")
|
|
req_line_splited = req_line.split(" ")
|
|
|
|
if len(req_line_splited) != 3:
|
|
logging.debug("Request head too long")
|
|
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()
|
|
|
|
def http_header_handle(self):
|
|
while True:
|
|
raw = self.read_fb.readline(MAX_REQLINE + 1)
|
|
|
|
if len(raw) > MAX_REQLINE:
|
|
logging.debug("Request header line too long")
|
|
self.conn.send(b"")
|
|
self.conn.close()
|
|
return
|
|
|
|
if raw == b"":
|
|
self.conn.send(b"")
|
|
self.conn.close()
|
|
return
|
|
|
|
if raw == b"\r\n":
|
|
self.http_data_handle()
|
|
break
|
|
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(" ")})
|
|
|
|
def http_data_handle(self):
|
|
# TODO: Get from headers Content-Length and get body from request
|
|
"""
|
|
|
|
raw = self.read_fb.readline(1024)
|
|
|
|
if len(raw) == 1024:
|
|
while True:
|
|
self.data += raw
|
|
|
|
if len(raw) < 1024:
|
|
break
|
|
else:
|
|
raw = self.read_fb.readline(1024)
|
|
"""
|
|
|
|
logging.debug(self.http_type)
|
|
logging.debug(self.http_address)
|
|
logging.debug(self.headers)
|
|
logging.debug(self.data)
|
|
|
|
self.send_form_data()
|
|
|
|
def send_form_data(self):
|
|
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]:
|
|
found = True
|
|
|
|
if url_metadata[1] == "static-file":
|
|
r = Response(data=fileiter(url_metadata[2]))
|
|
|
|
for i in r:
|
|
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")
|
|
|
|
for i in r:
|
|
self.write_fb.write(i)
|
|
self.write_fb.flush()
|
|
|
|
if not found:
|
|
r = Response(status_code=404, data=b"Not found!")
|
|
|
|
for i in r:
|
|
self.write_fb.write(i)
|
|
self.write_fb.flush()
|
|
|
|
self.write_fb.close()
|
|
self.read_fb.close()
|
|
self.conn.close()
|
|
|
|
|
|
def start():
|
|
logging.basicConfig(filename="main.log", filemode="a", encoding="UTF-8",
|
|
level=logging.DEBUG, format="[%(asctime)s][%(levelname)s] %(message)s")
|
|
|
|
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)
|
|
main_server_socket.bind(("0.0.0.0", config.SETUP["server"]["port"]))
|
|
main_server_socket.listen()
|
|
|
|
while True:
|
|
conn, addr = main_server_socket.accept()
|
|
logging.info("Accept connection from %s", addr[0])
|
|
|
|
thr_serv_conn = threading.Thread(target=HTTPHandler, args=(conn, addr,))
|
|
thr_serv_conn.run()
|
|
|
|
except KeyboardInterrupt:
|
|
logging.info("Server get keyboard interrupt. Initialize server to stop")
|