import socket import os from datetime import datetime import pathlib import html server_start = datetime.now() import pgs print(pgs.add(1,2)) def get_server_uptime(): seconds = (datetime.now() - server_start).total_seconds() if seconds > 60*60: # hour return f"{int(seconds/60/60)} hours" elif seconds > 60: # minute return f"{int(seconds/60)} minutes" return f"{seconds} seconds" # Hostname, only resolved at startup. hostname = socket.gethostname() # Specify environment based on hostname. env = "dev" if hostname.startswith("retoor") else "prod" # This dict will contain the connections in this format: {downstream_fd:upstream_fd] streams = {} # This is debug variable. It holds the number of connections total made. counter = 0 def is_ssh(header_bytes): return b'SSH' in header_bytes def is_http(header_bytes): """ Check if the header is an HTTP request. """ return b'HTTP/1.1' in header_bytes or b'HTTP/1.0' in header_bytes or b'HTTP/2.0' in header_bytes or b'HTTP/3.0' in header_bytes or b'Connection: ' in header_bytes def is_https(header_bytes): return not any([is_ssh(header_bytes), is_http(header_bytes)]) def route(downstream,upstream): """ This is a connection router which will be called by the server every time a client connects. This function will be used to determine the upstream. The downstream and upstream are file descriptors. The upstream is not connected yet, it only holds a file descriptor. The connection will be made in this function. The connection will be set non blocking after this function by pgs. This way of routing is so dynamic that you can: - Create [your-site].localhost redirects without configuring DNS. - Run multiple services on the same port. It is possible to run ssh on the same port as your HTTPS server. This is a good idea in sense of security. Very unique, who does / expects that? - Rewrite urls in general. - Make clients always connect to the same upstream. Servers only have to manage their own session instead of having to communicate with redis. - You can inject headers in the request. - You can add HTTP Basic Authentication to protect all your services in a very early stage. - This server is quick, it can act as ddos protection. - You can make your server act as a load balancer. - You can make your server act as a reverse proxy. - You can apply rate limits. - You can cache responses. - You can implement a complete custom protocol here. Complete own design. This feature will probably moved in the future. - You can do static file serving. - You can protect sensitive data not leaving the network by intercepting it. - You can call AI to make modications. - You can call databases. - You can save statistics. """ global streams global counter counter += 1 print("Connection nr.", counter) u = socket.fromfd(upstream, socket.AF_INET, socket.SOCK_STREAM) #u = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #print("FD:",u.fileno()) peek = pgs.read(downstream, 4096).tobytes() if pgs.is_ssh(peek): print("Forwarding to ssh molodetz") u.connect(("molodetz.nl", 22)) elif pgs.is_http(peek): if b'/random' in peek or b'random.' in peek: print("Forwarding to 127.0.0.1:3028.") peek = peek.replace(b'/random', b'/') peek = peek.replace(b'random.', b'') u.connect(("127.0.0.1", 3028)) elif b'molodetz.local' in peek: print("Forwarding to 127.0.0.1:8082.") peek = peek.replace(b'molodetz.local', b'localhost') u.connect(("127.0.0.1", 8082)) elif b'bench.local' in peek: print("Responding with bench page.") body = f"""\n\nBenchmark page.\n\n\n

Bench

\n

{counter}

\n\n\n""".encode() s = socket.fromfd(downstream, socket.AF_INET, socket.SOCK_STREAM) s.sendall( b'HTTP/1.1 200 Pretty Good Server.\r\n' + b'Content-Length: ' + str(len(body)).encode() + b'\r\n' + b'Content-Type: text/html\r\n\r\n' + body ) u = None s.shutdown(socket.SHUT_RDWR) else: # 404 if env == "prod": pgs.write(downstream, b'HTTP/1.1 403 Authorization Required.\r\n\r\n') else: pgscript_source = html.escape(pathlib.Path(__file__).read_text()) content = f"""
Server: Pretty Good Server
Environment: {env}
Total connections: {counter}
Local hostname: {hostname}
Downstream FD: {downstream}
Upstream FD: {upstream}
Current time server: {datetime.now()}
Server started on: {server_start}
Server uptime: {get_server_uptime()}

Source code of pgscript.py

Location: {pathlib.Path(__file__).resolve()}
{pgscript_source}
""" body = f"""\n\nDebug page.\n\n\n

Pretty Good Server

\n

Debugging information

\n

{content}

\n\n\n""" headers = ["HTTP/1.1 200 Pretty Good Server.", "Content-Length: {len(body)}", "Content-Type: text/html", "" ] headers = "\r\n".join(headers) response = f"{headers}{body}" pgs.write(downstream,response) # Unset socket so the server will close it. # Do not disconnect in python! # Instead of a 404, we also could've displayed a custom page. # Maybe some server statistics? u = None elif is_https(peek) and env == "prod": print("Forwarding to dev.to") u.connect(("devrant.com", 443)) peek = peek.replace(b'localhost', b'devrant.com') peek = peek.replace(b'molodetz.nl', b'devrant.com') else: # Error. print("Could not find upstream for header content.") print(f"Closing connection. Your current environment: {env}") # Don't have to close socket, pgs will do that himself. # Socket handling is done at one place to avoid race conditions. u = None if not u: return -1 # Remove reference to the socket so it doesn't get garbage collected. # This could break the connection. This way, it stays open. u = None os.write(upstream,peek) # Keep track of connections. Not sure if this is needed. streams[downstream] = upstream streams[upstream] = downstream # Return exact same value as what is given as parameter. return upstream