#!/usr/bin/env python3 """ 自定义静态文件服务器,支持自定义404页面和SPA路由 用于部署Next.js静态导出的文件 """ import http.server import socketserver import os import sys import urllib.parse from pathlib import Path class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): # 设置静态文件目录 super().__init__(*args, directory="out", **kwargs) def end_headers(self): # 添加安全头和缓存控制 self.send_header('X-Content-Type-Options', 'nosniff') self.send_header('X-Frame-Options', 'DENY') self.send_header('X-XSS-Protection', '1; mode=block') # 为HTML文件禁用缓存,为静态资源启用缓存 if self.path.endswith('.html'): self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') self.send_header('Pragma', 'no-cache') self.send_header('Expires', '0') elif any(self.path.endswith(ext) for ext in ['.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.woff', '.woff2']): self.send_header('Cache-Control', 'public, max-age=31536000') super().end_headers() def do_GET(self): # URL解码 parsed_path = urllib.parse.unquote(self.path) # 移除查询参数 if '?' in parsed_path: parsed_path = parsed_path.split('?')[0] # 移除fragment if '#' in parsed_path: parsed_path = parsed_path.split('#')[0] # 构建文件路径 if parsed_path == '/': # 根路径重定向到index.html file_path = Path("out/index.html") else: # 移除开头的斜杠 clean_path = parsed_path.lstrip('/') file_path = Path("out") / clean_path # 如果是目录,尝试查找index.html if file_path.is_dir(): file_path = file_path / "index.html" # 如果没有扩展名,尝试添加.html elif not file_path.suffix and not file_path.exists(): file_path = file_path.with_suffix('.html') print(f"请求路径: {self.path}") print(f"解析路径: {parsed_path}") print(f"文件路径: {file_path}") print(f"文件存在: {file_path.exists()}") # 检查文件是否存在 if file_path.exists() and file_path.is_file(): # 文件存在,正常处理 self.path = '/' + str(file_path.relative_to("out")) super().do_GET() else: # 文件不存在,返回404页面 self.send_custom_404() def send_custom_404(self): """发送自定义404页面""" try: # 检查自定义404页面是否存在 error_page_path = Path("out/404.html") if not error_page_path.exists(): # 如果404.html不存在,使用public/404.html error_page_path = Path("public/404.html") if error_page_path.exists(): with open(error_page_path, 'rb') as f: content = f.read() self.send_response(404) self.send_header('Content-type', 'text/html; charset=utf-8') self.send_header('Content-Length', str(len(content))) self.end_headers() self.wfile.write(content) print(f"返回自定义404页面: {error_page_path}") else: # 如果自定义404页面也不存在,返回简单的404 self.send_simple_404() except Exception as e: print(f"发送自定义404页面时出错: {e}") self.send_simple_404() def send_simple_404(self): """发送简单的404响应""" message = """
抱歉,您访问的页面不存在。
""" content = message.encode('utf-8') self.send_response(404) self.send_header('Content-type', 'text/html; charset=utf-8') self.send_header('Content-Length', str(len(content))) self.end_headers() self.wfile.write(content) def log_message(self, format, *args): """自定义日志格式""" print(f"[{self.log_date_time_string()}] {format % args}") def main(): # 检查out目录是否存在 if not os.path.exists("out"): print("错误: 'out' 目录不存在。请先运行构建命令生成静态文件。") sys.exit(1) # 设置端口 PORT = 8080 if len(sys.argv) > 1: try: PORT = int(sys.argv[1]) except ValueError: print("警告: 无效的端口号,使用默认端口 8080") # 启动服务器 try: with socketserver.TCPServer(("", PORT), CustomHTTPRequestHandler) as httpd: print(f"静态文件服务器已启动") print(f"访问地址: http://localhost:{PORT}") print(f"静态文件目录: {os.path.abspath('out')}") print("按 Ctrl+C 停止服务器") print("-" * 50) httpd.serve_forever() except KeyboardInterrupt: print("\n服务器已停止") except OSError as e: if e.errno == 48: # Address already in use print(f"端口 {PORT} 已被占用,请尝试其他端口") print(f"使用方法: python {sys.argv[0]} [端口号]") else: print(f"启动服务器时出错: {e}") sys.exit(1) if __name__ == "__main__": main()