""" The mighty silly webserver written in python for no good reason """ import time import json import gzip import socket import asyncio import htmlmin import aiofiles import threading # some constants PACKET_SIZE = 2048 PORT = 13700 # using random port cuz why not # response status codes RESPONSE = { 200: b'OK', 400: b'Bad Request', 401: b'Unauthorized', 403: b'Forbidden', 404: b'Not Found', 6969: b'UwU' } def get_response(code: int) -> bytes: return str(code).encode("ascii") + RESPONSE.get(code, b':(') def is_alive(sock: socket.socket) -> bool: """ Checks if the socket is still alive :param sock: socket :return: boolean (true if socket is alive, false otherwise) """ return getattr(sock, '_closed', False) def decode_request(req: str) -> dict[str, str | list | None]: # request dictionary request = dict() # request type and path request["type"] = req[:6].split(" ")[0] request["path"] = req[len(request["type"]) + 1:req.find("\r\n")].split(" ")[0] # decode other headers for line in req.split("\r\n")[1:]: if len(split := line.split(":")) == 2: key, value = split value = value.lstrip(" ") # write key value pair request[key] = value return request class HTMLServer: """ The very cool webserver """ def __init__(self): self.sock: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.clients: list[socket.socket] = [] # list of allowed paths self.allowed_paths: dict[str, dict] = { "/": {"path": "www/index.html", "encoding": "css/html"}, "/robots.txt": {"path": "www/robots.txt", "encoding": "text"}, "/favicon.ico": {"path": "www/favicon.ico", "encoding": "bin"}, "/css/styles.css": {"path": None, "encoding": "css/html"}, } def run(self): """ Function that starts the webserver """ # bind the server to port and start listening self.sock.bind(('', PORT)) self.sock.listen() # start running thread t = threading.Thread(target=self._run, daemon=True) t.start() # keep alive try: while True: time.sleep(0.1) except KeyboardInterrupt: self.sock.close() print("Closed.") def _run(self): """ Run function for threads :return: """ asyncio.run(self.server_listener()) async def server_listener(self): """ Listens for new connections, and handles them """ while True: client, address = self.sock.accept() self.clients.append(client) await self.server_handle(client) async def server_handle(self, client: socket.socket): """ Handles the actual connections (clients) :param client: connection socket """ # message buffer buffer = bytearray() while True: # try to fetch a message # die otherwise try: message = client.recv(PACKET_SIZE) except OSError: break if message == b'': break # append packet to buffer buffer += message # check EoF (2 blank lines) if buffer[-4:] == b'\r\n\r\n': # text buffer text_buffer = buffer.decode("ascii") # decode request request = decode_request(text_buffer) print(f"[{request['type']}] Request from client '{client.getpeername()[0]}'") # log that request async with aiofiles.open("logs.log", "a") as f: await f.write( json.dumps( { "client": client.getpeername()[0], "request": request }, indent=2 ) + "\n" ) # handle the request if request["type"] == "GET": await self.handle_get_request(client, request) else: await self.handle_other_request(client) # clear buffer buffer.clear() client.close() self.clients.remove(client) async def handle_get_request(self, client: socket.socket, req: dict[str, str | None]): # if it's yandex if req.get("from") == "support@search.yandex.ru": response = get_response(404) data = b'Nothing...' # check UwU path elif req["path"] == "/UwU" or req["path"] == "/U/w/U": response = get_response(6969) data = b'