glitchcraft/server.py

148 lines
5.2 KiB
Python
Raw Normal View History

#!/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, fmt, *args):
# Custom log format with timestamp
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(f"[{timestamp}] {fmt % 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://<your-ip>:{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://<your-ip>:{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()