""" World Sketcher 本地保存服务器 用法: python server.py [port] 然后浏览器打开 http://localhost:/sketch.html """ import http.server, json, os, sys, urllib.parse from datetime import datetime PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8765 SERVE_DIR = os.path.dirname(os.path.abspath(__file__)) OUTPUT_DIR = os.path.join(SERVE_DIR, "output") os.makedirs(OUTPUT_DIR, exist_ok=True) class Handler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, directory=SERVE_DIR, **kwargs) # ── POST /save ── 保存文件到 output/ def do_POST(self): if self.path != "/save": self.send_error(404); return ctype = self.headers.get("Content-Type", "") if ctype.startswith("multipart/form-data"): boundary = ctype.split("boundary=")[-1].encode() data = self.rfile.read(int(self.headers["Content-Length"])) # 简易 multipart 解析 parts = data.split(b"--" + boundary) saved = [] for part in parts: if not part or part.strip() in (b"", b"--\r\n", b"--"): continue header_end = part.find(b"\r\n\r\n") if header_end < 0: continue header = part[:header_end].decode("utf-8", errors="replace") body = part[header_end + 4:] if body.endswith(b"\r\n"): body = body[:-2] # 提取文件名 fname = None for seg in header.split(";"): seg = seg.strip() if seg.startswith("filename="): fname = seg.split("=", 1)[1].strip('"') break if not fname: continue # 安全:只取文件名,禁止路径遍历 fname = os.path.basename(fname) fpath = os.path.join(OUTPUT_DIR, fname) with open(fpath, "wb") as f: f.write(body) saved.append(fname) resp = json.dumps({"ok": True, "saved": saved}, ensure_ascii=False) self.send_response(200) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(resp.encode("utf-8")) print(f" [{datetime.now():%H:%M:%S}] Saved: {', '.join(saved)}") else: self.send_error(400, "Expected multipart/form-data") # ── GET 路由 ── def do_GET(self): # /list → 列出 output/ 下可用文件 if self.path == "/list": files = sorted( f for f in os.listdir(OUTPUT_DIR) if f.endswith((".json", ".zip")) ) resp = json.dumps({"files": files}, ensure_ascii=False) self.send_response(200) self.send_header("Content-Type", "application/json; charset=utf-8") self.end_headers() self.wfile.write(resp.encode("utf-8")) return # /load/ → 从 output/ 读取文件 if self.path.startswith("/load/"): fname = os.path.basename(urllib.parse.unquote(self.path[6:], encoding="utf-8")) fpath = os.path.join(OUTPUT_DIR, fname) if not os.path.isfile(fpath): self.send_error(404); return self.send_response(200) if fname.endswith(".zip"): self.send_header("Content-Type", "application/zip") elif fname.endswith(".json"): self.send_header("Content-Type", "application/json") self.end_headers() with open(fpath, "rb") as f: self.wfile.write(f.read()) return # 其他 → 静态文件(sketch.html 等) super().do_GET() def log_message(self, format, *args): # 只显示非静态资源的请求日志 if "/save" in str(args) or "/list" in str(args) or "/load/" in str(args): super().log_message(format, *args) if __name__ == "__main__": with http.server.HTTPServer(("", PORT), Handler) as httpd: url = f"http://localhost:{PORT}/sketch.html" print(f"World Sketcher server running") print(f" URL: {url}") print(f" Output: {OUTPUT_DIR}") print(f" Press Ctrl+C to stop.\n") try: httpd.serve_forever() except KeyboardInterrupt: print("\nServer stopped.")