#!/usr/bin/env python3 """ Simple HTTP server for testing the GlitchCraft PWA Run with: python3 server.py Supports both IPv4 and IPv6 connections """ import http.server import socketserver import socket import os import sys import time PORT = 8000 # Serve from the app directory DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "app") class GlitchCraftHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): """Custom HTTP request handler for serving the GlitchCraft PWA.""" def __init__(self, *args, **kwargs): super().__init__(*args, directory=DIRECTORY, **kwargs) def end_headers(self): # Add headers for PWA functionality self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") self.send_header("Service-Worker-Allowed", "/") self.send_header("X-Content-Type-Options", "nosniff") # Add CORS headers for development self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") self.send_header("Access-Control-Allow-Headers", "*") super().end_headers() def log_message(self, format, *args): # pylint: disable=redefined-builtin # Custom log format with timestamp timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) print(f"[{timestamp}] {format % args}") class DualStackTCPServer(socketserver.TCPServer): """TCP server that attempts to support both IPv4 and IPv6.""" allow_reuse_address = True def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): # Try IPv6 first (with dual-stack if available) self.address_family = socket.AF_INET6 try: super().__init__( server_address, RequestHandlerClass, bind_and_activate=False ) # Try to enable dual-stack (works on most modern systems) try: self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) self._is_dual_stack = True except (AttributeError, OSError): # Dual-stack not supported, IPv6 only self._is_dual_stack = False if bind_and_activate: self.server_bind() self.server_activate() except (OSError, socket.error): # IPv6 failed, fall back to IPv4 self.server_close() self.address_family = socket.AF_INET self._is_dual_stack = False # Convert IPv6 address to IPv4 if needed if server_address[0] == "::": server_address = ("0.0.0.0", server_address[1]) super().__init__(server_address, RequestHandlerClass, bind_and_activate) def main(): """Start the development server.""" print("Starting GlitchCraft development server...") print(f"Serving files from: {DIRECTORY}") print(f"Port: {PORT}") # Check if app directory exists if not os.path.exists(DIRECTORY): print(f"Error: Directory {DIRECTORY} does not exist!") sys.exit(1) # Try different host configurations hosts_to_try = [ ("::", "IPv6 dual-stack"), ("0.0.0.0", "IPv4 all interfaces"), ("127.0.0.1", "IPv4 localhost only"), ] server = None for host, description in hosts_to_try: try: server = DualStackTCPServer((host, PORT), GlitchCraftHTTPRequestHandler) print(f"✓ Server started using {description}") # Print access URLs based on server configuration print("\nAccess your app at:") if server.address_family == socket.AF_INET6: if hasattr(server, "_is_dual_stack") and getattr( server, "_is_dual_stack", False ): print(f" http://localhost:{PORT}/") print(f" http://127.0.0.1:{PORT}/ (IPv4)") print(f" http://[::1]:{PORT}/ (IPv6)") print(f" http://:{PORT}/ (network)") else: print(f" http://[::1]:{PORT}/ (IPv6 only)") print(f" http://localhost:{PORT}/ (if supported)") else: if host == "0.0.0.0": print(f" http://localhost:{PORT}/") print(f" http://127.0.0.1:{PORT}/ (IPv4)") print(f" http://:{PORT}/ (network)") else: print(f" http://localhost:{PORT}/ (localhost only)") print("\nPress Ctrl+C to stop the server") break except OSError as e: print(f"✗ Failed to start server on {host}: {e}") if "Address already in use" in str(e): print( f" Port {PORT} is already in use. Try a different port or stop other servers." ) continue if server is None: print("Failed to start server on any interface!") sys.exit(1) try: server.serve_forever() except KeyboardInterrupt: print("\nShutting down server...") finally: server.server_close() if __name__ == "__main__": main()