initial
This commit is contained in:
122
namepicker/webserver.py
Normal file
122
namepicker/webserver.py
Normal file
@@ -0,0 +1,122 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user