AwsLinker/scripts/serve-static.py

172 lines
6.1 KiB
Python
Raw Normal View History

2025-09-16 17:19:58 +08:00
#!/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 = """
<!DOCTYPE html>
<html>
<head>
<title>404 - 页面未找到</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #333; }
p { color: #666; }
a { color: #0066cc; text-decoration: none; }
</style>
</head>
<body>
<h1>404 - 页面未找到</h1>
<p>抱歉您访问的页面不存在</p>
<p><a href="/">返回首页</a></p>
</body>
</html>
"""
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()