4 Commits
Author SHA1 Message Date
Nova bab41a0084 Merge branch '2.0' of https://git.novacow.ch/Nova/PyWebServer into 2.0 2026-08-10 16:35:14 +02:00
Nova cec8a80714 First feature-freeze code cleanup version 2026-08-10 16:34:45 +02:00
Nova ba9c2dff67 Update CHANGELOG.md 2026-08-10 16:03:33 +02:00
Nova 39a92ede21 Update README.md 2026-08-10 16:03:10 +02:00
3 changed files with 16 additions and 46 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
* Fixed bugs pertaining to proxy
* Attempted fix at hanging socket by introducing a default 25 second timeout.
* Attempted fix at hanging socket by introducing a default 2 second timeout.
## Configuration changes
+4 -2
View File
@@ -9,15 +9,17 @@ Once a milestone is hit (e.g. a new feature fully implemented), I'll publish a r
## Approaching 1.0.0!
Amethyst is finally approaching 1.0.0! Very very soon I will feature-freeze the project and begin just fixing bugs and cleaning up code! This may take a bit because the codebase is very cluttered, and because all features are there in a basic state, it would be better to fix and clean up what I have, so I have a workable codebase for implementing new features, and because new features aren't going to be added anyway, I might as well fully release the project!
I have feature-freezed the project! Any further release is just cleaning up the codebase and getting some snazzy new default webpage or something idk yet xdd.
## Currently working features:
* New configuration is ~95% done, most features work.
* New configuration!
* Fixed **A LOT** of unreported bugs from the old code.
* More resilliency against errors.
* Improved security.
* Proxy almost working!
* Multithreading!
* Python Execution Script!
## Project status:
+11 -43
View File
@@ -47,9 +47,8 @@ import threading
import ssl
import socket
import signal
# Experimental imports go here
import select
import subprocess
# Experimental imports go here
try:
if not os.getcwd() in sys.path:
@@ -63,7 +62,7 @@ except ImportError:
)
# pass
AMETHYST_BUILD_NUMBER = "0.3.1-0130-mt-tryout2"
AMETHYST_BUILD_NUMBER = "0.99.0-0134-ff"
AMETHYST_REPO = "https://git.novacow.ch/Nova/PyWebServer/"
@@ -115,7 +114,6 @@ class ConfigParser:
if host:
value = self.data["hosts"].get(host, {}).get(key)
elif key == "hosts":
print(f"\n\n\nHosts!\nHosts: {self.data['hosts']}\n\n\n")
value = list(self.data["hosts"].keys())
else:
value = self.data["globals"].get(key)
@@ -172,9 +170,6 @@ class FileHandler:
return 0
def read_config(self, key, host_name=None):
print(
f"\n\n\nQuery!\nkey: {key}\nhost_name: {host_name}\nret: {self.cfg.query_config(key, host_name)}"
)
return self.cfg.query_config(key, host_name)
def autocert(self):
@@ -190,7 +185,6 @@ class RequestParser:
def __init__(self):
self.file_handler = FileHandler()
self.hosts = self.file_handler.read_config("hosts")
print(f"Hosts: {self.hosts}")
def extract_header(self, header: str, request: bytes | str):
if isinstance(request, bytes):
@@ -260,7 +254,6 @@ class RequestParser:
Mfw im in an ugly code writing contest and my opponent is nova while writing a side project
"""
host = f"{host}"
print(f"hosts: {self.hosts}, host: {host}, split: {host.rsplit(':', 1)[0]}")
if ":" in host:
host = host.rsplit(":", 1)[0]
host = host.lstrip()
@@ -296,9 +289,7 @@ class ProxyServer:
def try_connection(
self, host: str, port: int, data: bytes, chost: str, force_tls: bool = None
):
print(f"\n\n\nchost: {chost}\n\n\n")
nhost = self.file_handler.read_config("proxy", chost)
print(f"\n\n\nnhost: {nhost}\n\n\n")
# nhost will include http or https.
if nhost.startswith("https"):
nhost = nhost[6:-1]
@@ -313,16 +304,13 @@ class ProxyServer:
)
if force_tls is True:
do_tls = True
print(f"\n\n\nnhost: {nhost}\n\n\n")
if ":" in nhost:
nport = int(nhost.split(":")[1])
nhost = nhost.split(":")[0]
else:
nport = port
print(f"{nhost}, {nport}, {data}")
data = self.reset_host(nhost, nport, data)
try:
print("Waiting on TCP start.")
return self.tcp_send(nhost, nport, data, do_tls)
except Exception as e:
raise Exception(f"Server replied unexpected. Reply from Python subsystem: {e}")
@@ -378,7 +366,6 @@ class ProxyServer:
raw_sock, server_hostname=server_hostname
) as ssock:
ssock.sendall(data)
print("data reached")
resp = self.recv_all(ssock)
if self.rq.extract_header("Transfer-Encoding", resp) == "chunked":
ssock.sendall(b"TRANSER-ENCODING IS NOT SUPPORTED")
@@ -393,9 +380,7 @@ class ProxyServer:
)
return resp
else:
print(f"\n\n\nraw data: {data}\n\n\n")
raw_sock.sendall(data)
print("Waiting for response...")
resp = self.recv_all(raw_sock)
if self.rq.extract_header("Transfer-Encoding", resp) is not None:
raw_sock.sendall(b"TRANSER-ENCODING IS NOT SUPPORTED")
@@ -454,11 +439,9 @@ class WebServer:
self.http_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
self.http_socket.bind(("::", self.http_port))
# self.http_socket.settimeout(1)
self.https_socket_raw = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
self.https_socket_raw.bind(("::", self.https_port))
# self.https_socket_raw.settimeout(1)
self.proxy_handler = ProxyServer(self.file_handler)
@@ -493,20 +476,27 @@ class WebServer:
def start(self, http, https):
signal.signal(signal.SIGINT, self.shutdown)
signal.signal(signal.SIGTERM, self.shutdown)
http_thread = threading.Thread(target=self.start_http, daemon=True)
https_thread = threading.Thread(target=self.start_https, daemon=True)
if https is True:
if self.skip_ssl is True:
print("WARN: You have enabled HTTPS without SSL!!")
yn = input("Is this intended behaviour? [y/N] ")
if yn.lower() == "n":
exit(1)
self.start_https()
https_thread.start()
else:
self.https_socket.close()
if http is True:
self.start_http()
http_thread.start()
else:
self.http_socket.close()
http_thread.join()
https_thread.join()
def start_http(self):
self.http_socket.listen(5)
print(f"HTTP server listening on port {self.http_port}...")
@@ -535,7 +525,6 @@ class WebServer:
except OSError as e:
if not self.running:
break
print(f"OSError! {e}")
continue
except Exception as e:
print(f"HTTP error: {e}")
@@ -568,7 +557,6 @@ class WebServer:
except OSError as e:
if not self.running:
break
print(f"OSError! {e}")
continue
except Exception as e:
print(f"HTTPS error: {e}")
@@ -591,14 +579,10 @@ class WebServer:
if line.lower().startswith(b"content-length:"):
content_length = int(line.split(b":")[1].strip())
# print(f"Content-Length to server: {content_length}")
# Read body
body = rest
print(f"Rest length: {len(rest)}")
while len(body) < content_length:
chunk = conn.recv(4096)
# print(f"\n\nrecv returned {len(chunk)}\n\n")
if not chunk:
print("\n\nsocket closed\n\n")
break
@@ -618,7 +602,6 @@ class WebServer:
if isinstance(response, str):
response = response.encode()
print(len(response))
conn.sendall(response)
except Exception as e:
print(f"Error handling connection: {e}")
@@ -633,10 +616,8 @@ class WebServer:
conn.close()
def handle_request(self, data, addr):
# print(f"data: {data}")
request_line = data.splitlines()[0]
# Extract host from headers, never works though
for line in data.splitlines():
if "Host" in line:
host = line.split(":", 1)[1].strip()
@@ -735,7 +716,6 @@ class WebServer:
file_content, mimetype = self.file_handler.read_file(path, directory)
if file_content == 403:
print("WARN: Directory traversal attack prevented.") # look ma, security!!
return self.build_response(403, self.http_403_html)
if file_content == 404:
return self.build_response(404, self.http_404_html)
@@ -789,7 +769,6 @@ class WebServer:
200: "OK",
204: "No Content",
302: "Found",
304: "Not Modified", # TODO KEKL
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
@@ -803,8 +782,6 @@ class WebServer:
if isinstance(body, str):
body = body.encode()
# TODO: dont encode yet, and i encode. awesome comments here.
# Don't encode yet, if 302 status code we have to include location.
headers = (
f"HTTP/1.1 {status_code} {status_message}\r\n"
f"Server: Amethyst/build-{AMETHYST_BUILD_NUMBER}\r\n"
@@ -813,13 +790,6 @@ class WebServer:
).encode()
if status_code == 302:
# 302 currently only happens when the reload is triggered.
# Why not 307, Moved Permanently? Because browsers will cache the
# response and not send the reload command.
# if port == 443:
# host = f"https://{host}/"
# else:
# host = f"http://{host}/"
headers = (
f"HTTP/1.1 {status_code} {status_message}\r\n"
f"Location: {host}\r\n"
@@ -864,9 +834,7 @@ def main():
http_port = file_handler.read_config("port")
https_port = file_handler.read_config("https-port")
http_enabled = bool(file_handler.read_config("http"))
print(http_enabled)
https_enabled = bool(file_handler.read_config("https"))
print(https_enabled)
server = WebServer(http_port=http_port, https_port=https_port)
server.start(http_enabled, https_enabled)