#!/usr/bin/env python3 """ Simple HTTP server for WebSocket client testing with password protection. Usage: python3 start_web_server.py [--port PORT] [--password PASSWORD] Open in browser: http://localhost:PORT/client.html """ import argparse import base64 import http.server import socketserver import os import sys import threading from pathlib import Path PORT = 8080 SCRIPT_DIR = Path(__file__).parent.resolve() # Default password DEFAULT_PASSWORD = "admin" class AuthHandler(http.server.SimpleHTTPRequestHandler): """HTTP handler with Basic Auth.""" def __init__(self, *args, password="admin", **kwargs): self.password = password super().__init__(*args, **kwargs) def check_auth(self): """Check Basic Auth credentials.""" auth_header = self.headers.get('Authorization') if not auth_header: return False try: # Decode "Basic base64(username:password)" auth_type, auth_string = auth_header.split(' ', 1) if auth_type.lower() != 'basic': return False decoded = base64.b64decode(auth_string).decode('utf-8') username, password = decoded.split(':', 1) # For now, we only check password (username is ignored) return password == self.password except Exception: return False def send_auth_request(self): """Send 401 Unauthorized response.""" self.send_response(401) self.send_header('WWW-Authenticate', 'Basic realm="Wyoming Satellite"') self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() html = ''' 401 Unauthorized

Wyoming Satellite - Authentication Required

Please enter the password to access the Web UI.

''' self.wfile.write(html.encode('utf-8')) def do_GET(self): """Handle GET requests with authentication.""" # Skip auth for static files that are already protected if not self.check_auth(): self.send_auth_request() return super().do_GET() print(f"[WebServer] ✅ {self.requestline} → authenticated") def log_message(self, format, *args): # Suppress default logging pass def create_auth_handler(password): """Create auth handler with password.""" class Handler(AuthHandler): def __init__(self, *args, **kwargs): super().__init__(*args, password=password, **kwargs) return Handler class ReusableTCPServer(socketserver.TCPServer): """TCP Server with address reuse enabled.""" allow_reuse_address = True def main(): parser = argparse.ArgumentParser(description='WebSocket Client Web Server with Password Protection') parser.add_argument('--port', type=int, default=PORT, help=f'Port (default: {PORT})') parser.add_argument('--host', default='0.0.0.0', help='Host (default: 0.0.0.0)') parser.add_argument('--password', default=DEFAULT_PASSWORD, help=f'Password (default: {DEFAULT_PASSWORD})') args = parser.parse_args() # Change to script directory os.chdir(SCRIPT_DIR) # Create handler with password Handler = create_auth_handler(args.password) print(""" +==========================================================+ | Wyoming Satellite WebSocket Client Server | +==========================================================+ | Serving: {:<53} | | URL: http://localhost:{:<29} | | Remote: http://:{:<25} | | Password: {:<48} | | | | Press Ctrl+C to stop | +==========================================================+ """.format( str(SCRIPT_DIR), args.port, args.port, '*' * len(args.password) )) try: with ReusableTCPServer((args.host, args.port), Handler) as httpd: print(f"Server started on http://{args.host}:{args.port}") print("Serving files from:", SCRIPT_DIR) print(f"Password protected ({len(args.password)} characters)") print("\nPress Ctrl+C to stop\n") try: httpd.serve_forever() except KeyboardInterrupt: print("\nShutting down gracefully...") # Shutdown in a separate thread to avoid deadlock shutdown_thread = threading.Thread(target=httpd.shutdown) shutdown_thread.start() shutdown_thread.join(timeout=2) httpd.server_close() print("Server stopped successfully") except OSError as e: print(f"\nError: Could not bind to port {args.port}") print(f" {e}") print("\nTry using a different port:") print(f" python3 {sys.argv[0]} --port 8081\n") sys.exit(1) except Exception as e: print(f"\nUnexpected error: {e}") sys.exit(1) if __name__ == "__main__": main()