_server.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. # -*- coding: utf-8 -*-
  2. # 静态文件服务:对 Trae 预览注入的 /@vite/* 热更新脚本返回 200 空JS,
  3. # 避免 404 导致预览面板中断主文档加载(net::ERR_ABORTED)
  4. import http.server
  5. import socketserver
  6. import os
  7. os.chdir(os.path.dirname(os.path.abspath(__file__)))
  8. class H(http.server.SimpleHTTPRequestHandler):
  9. # HTTP/1.1 keep-alive:避免 HTTP/1.0 每请求关连接,预览面板复用连接时遇到 RST 而 ERR_ABORTED
  10. protocol_version = "HTTP/1.1"
  11. def _vite(self):
  12. self.send_response(200)
  13. self.send_header("Content-Type", "text/javascript")
  14. self.send_header("Cache-Control", "no-store")
  15. self.send_header("Content-Length", "2")
  16. self.end_headers()
  17. self.wfile.write(b"//")
  18. def _strip_cond(self):
  19. # 剥离条件请求头 → 永远 200 全量正文:
  20. # 预览面板需改写 HTML 正文注入热更新脚本,304 无正文会使其放弃本次加载并重发(net::ERR_ABORTED)
  21. for h in ("If-Modified-Since", "If-None-Match"):
  22. if h in self.headers:
  23. del self.headers[h]
  24. def send_header(self, keyword, value):
  25. # 注意:Python 3.14 的 send_head 用的是 "Content-type"(小写t),比较必须忽略大小写
  26. if keyword.lower() == "content-type" and "text/html" in value:
  27. super().send_header("Cache-Control", "no-store")
  28. super().send_header(keyword, value)
  29. PAGE = "/AI%E5%BF%AB%E9%80%9F%E6%8A%A5%E4%BB%B7_Demo2.optimizing.html"
  30. def do_GET(self):
  31. # 根路径/短别名 → 同一页面:预览面板用最短的纯ASCII URL,避免长百分号编码URL
  32. q = self.path.split("?", 1)[0]
  33. if q in ("/", "/index.html", "/index"):
  34. self.path = self.PAGE + ("?" + self.path.split("?", 1)[1] if "?" in self.path else "")
  35. if self.path.startswith("/@vite/"):
  36. self._vite()
  37. elif self.path.startswith("/ref_parts"):
  38. # 读取参考报价表的 styles.xml / theme.xml(base64),供前端导出时原样套用
  39. import json, base64, zipfile
  40. try:
  41. rp = os.path.join(os.path.join(os.path.expanduser("~"), "Desktop"), "6.18号欧阳路预估报价.xlsx")
  42. z = zipfile.ZipFile(rp)
  43. theme = z.read("xl/theme/theme1.xml") if "xl/theme/theme1.xml" in z.namelist() else b""
  44. resp = json.dumps({"styles": base64.b64encode(z.read("xl/styles.xml")).decode(),
  45. "theme": base64.b64encode(theme).decode()}).encode()
  46. self.send_response(200)
  47. except Exception as e:
  48. resp = json.dumps({"err": str(e)}).encode()
  49. self.send_response(500)
  50. self.send_header("Content-Type", "application/json")
  51. self.send_header("Cache-Control", "no-store")
  52. self.send_header("Content-Length", str(len(resp)))
  53. self.end_headers()
  54. self.wfile.write(resp)
  55. else:
  56. self._strip_cond()
  57. super().do_GET()
  58. def do_HEAD(self):
  59. if self.path.startswith("/@vite/"):
  60. self._vite()
  61. else:
  62. self._strip_cond()
  63. super().do_HEAD()
  64. def do_POST(self):
  65. # 导出Excel:前端生成xlsx字节(base64) → 服务端写入桌面
  66. if self.path == "/save_xlsx":
  67. import json, base64
  68. try:
  69. ln = int(self.headers.get("Content-Length", 0))
  70. d = json.loads(self.rfile.read(ln).decode("utf-8"))
  71. name = os.path.basename(d["name"])
  72. data = base64.b64decode(d["data"])
  73. path = os.path.join(os.path.join(os.path.expanduser("~"), "Desktop"), name)
  74. with open(path, "wb") as f:
  75. f.write(data)
  76. resp = json.dumps({"ok": True, "path": path}, ensure_ascii=False).encode("utf-8")
  77. self.send_response(200)
  78. except Exception as e:
  79. resp = json.dumps({"ok": False, "err": str(e)}, ensure_ascii=False).encode("utf-8")
  80. self.send_response(500)
  81. self.send_header("Content-Type", "application/json")
  82. self.send_header("Cache-Control", "no-store")
  83. self.send_header("Content-Length", str(len(resp)))
  84. self.end_headers()
  85. self.wfile.write(resp)
  86. else:
  87. self.send_response(404)
  88. # HTTP/1.1 keep-alive 下必须给出 Content-Length,否则客户端会挂起等待正文
  89. self.send_header("Content-Length", "0")
  90. self.end_headers()
  91. socketserver.ThreadingTCPServer.allow_reuse_address = True
  92. s = socketserver.ThreadingTCPServer(("0.0.0.0", 8002), H)
  93. print("server ready on 8002")
  94. s.serve_forever()