#!/usr/bin/env python3 """ Simple HTTP server that works on both IPv4 and IPv6. It prints the client address (src) and the local address (dst) for each request. """ import logging import socket from http.server import BaseHTTPRequestHandler, HTTPServer from socketserver import TCPServer ## wrangled together by stackoverflow & duck.ai GPT-OSS 120B to add dualstack support class S(BaseHTTPRequestHandler): def _set_response(self): self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() def do_GET(self): logging.info( "GET request,\nPath: %s\nHeaders:\n%s\n", self.path, self.headers, ) self._set_response() self.wfile.write("
".encode())

        # echo request info
        self.wfile.write(
            f"GET request for {self.path} from {self.headers}\n".encode()
        )
        self.wfile.write(
            f"client (src_ip, src_port)   = {self.client_address}\n".encode()
        )
        self.wfile.write(
            f"local  (dst_ip, dst_port)   = {self.request.getsockname()}\n".encode()
        )
        self.wfile.write("
".encode()) def do_POST(self): length = int(self.headers.get('Content-Length', 0)) post_data = self.rfile.read(length) logging.info( "POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n", self.path, self.headers, post_data.decode('utf-8', errors='replace') ) self._set_response() self.wfile.write(f"POST request for {self.path}".encode()) def make_dualstack_server(host: str, port: int, handler): """ Return a TCPServer that listens on both IPv4 and IPv6. The socket is created, configured and bound **once** by us, then handed to TCPServer. We only call `server_activate()`. """ # 1️⃣ IPv6 socket sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) # 2️⃣ Enable dual‑stack (IPv4‑mapped IPv6) on platforms where the # default is IPv6‑only (macOS, Windows) sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) # 3️⃣ Reuse address – convenient during development sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 4️⃣ Bind the socket ourselves sock.bind((host, port)) # 5️⃣ Build the server **without** the automatic bind/activate step server = TCPServer( server_address=None, # not used because we bind manually RequestHandlerClass=handler, bind_and_activate=False, ) server.socket = sock # replace the internal socket # server.server_address is optional; we set it for nice repr() server.server_address = (host, port) # 6️⃣ Only activate (listen) – do NOT call server_bind() again server.server_activate() return server # ---------------------------------------------------------------------- # Main entry point # ---------------------------------------------------------------------- def run(host: str = "", port: int = 8080): """ Starts the HTTP server. * host = '' → listen on all interfaces (IPv4 & IPv6) * host = '::' → same as '' but explicit IPv6 * host = '0.0.0.0' → IPv4 only (still works with dual‑stack socket) """ logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') httpd = make_dualstack_server(host, port, S) logging.info(f"Starting HTTP server on {host or '::'}:{port} (dual‑stack)") try: httpd.serve_forever() except KeyboardInterrupt: logging.info("Keyboard interrupt – shutting down") finally: httpd.server_close() logging.info("Server stopped") if __name__ == '__main__': import sys if len(sys.argv) == 3: # usage: script.py run(host=sys.argv[1], port=int(sys.argv[2])) elif len(sys.argv) == 2: # usage: script.py run(port=int(sys.argv[1])) else: run()