#!/usr/bin/env python3 """极简静态服务器(带 SPA 路由回退)—— 用户空间,CentOS 自带 Python 3.6 即可。 托管前端构建产物(deploy 分支的 dist),供内网访问。客户端路由(如 /behavior/funnel/group)刷新时回退到 index.html,避免 404。 兼容 Python 3.6(不用 3.7+ 的 `directory=` 参数,改 os.chdir)。 用法: python3 serve.py <静态目录> [端口] 例: nohup python3 ~/serve-hs.py ~/hs-data 6666 > ~/hs-web.log 2>&1 & """ import os import sys from http.server import SimpleHTTPRequestHandler from socketserver import ThreadingTCPServer ROOT = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else ".") PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 6666 os.chdir(ROOT) class SpaHandler(SimpleHTTPRequestHandler): # 覆盖 send_head(GET 与 HEAD 都走它),统一做 SPA 回退。 def send_head(self): rel = self.path.split("?", 1)[0].split("#", 1)[0].lstrip("/") if rel and not os.path.isfile(os.path.join(ROOT, rel)): last = rel.rsplit("/", 1)[-1] # 仅"看起来像路由"的路径(末段无扩展名)回退到 index.html; # 缺失的资源文件(如 /assets/x.js)让其正常 404,避免返回 HTML 触发 MIME 错误。 if "." not in last: self.path = "/index.html" return SimpleHTTPRequestHandler.send_head(self) def end_headers(self): # index.html 不缓存(构建产物自带 hash,可长缓存)。 if self.path.split("?", 1)[0] in ("/", "/index.html"): self.send_header("Cache-Control", "no-cache") SimpleHTTPRequestHandler.end_headers(self) class Server(ThreadingTCPServer): allow_reuse_address = True daemon_threads = True if __name__ == "__main__": print("serving %s on 0.0.0.0:%d (SPA fallback)" % (ROOT, PORT)) Server(("0.0.0.0", PORT), SpaHandler).serve_forever()