122 lines
4.0 KiB
Python
122 lines
4.0 KiB
Python
#!/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("<body><pre>".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("</pre></body>".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 <host> <port>
|
||
run(host=sys.argv[1], port=int(sys.argv[2]))
|
||
elif len(sys.argv) == 2:
|
||
# usage: script.py <port>
|
||
run(port=int(sys.argv[1]))
|
||
else:
|
||
run() |