#!/usr/bin/env python3 """ local_pos_proxy.py — 本機 POS Proxy 在「接收端電腦」執行,監聽 127.0.0.1:18080 讓瀏覽器透過此 Proxy 發動 HTTP request 到 POS 設備,繞過瀏覽器 CORS 限制。 使用方式: python local_pos_proxy.py python local_pos_proxy.py --port 18080 # 自訂 port python local_pos_proxy.py --host 0.0.0.0 # 對外監聽(同網段其他裝置也可使用) 需求: pip install requests """ import json import argparse from http.server import HTTPServer, BaseHTTPRequestHandler try: import requests except ImportError: print("[ERROR] 請先安裝:pip install requests") exit(1) class PosProxyHandler(BaseHTTPRequestHandler): def _cors(self): self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type") # Chrome Private Network Access policy(公開頁面存取 localhost 需要此 header) self.send_header("Access-Control-Allow-Private-Network", "true") def do_OPTIONS(self): """回應 CORS preflight 請求""" self.send_response(200) self._cors() self.end_headers() def do_GET(self): if self.path == "/health": self._respond(200, {"status": "ok", "proxy": "local_pos_proxy"}) else: self._respond(404, {"error": "not found"}) def do_POST(self): try: length = int(self.headers.get("Content-Length", 0)) body = json.loads(self.rfile.read(length)) except Exception: self._respond(400, {"error": "invalid request body"}) return method = (body.get("method") or "GET").upper() url = (body.get("url") or "").strip() payload = body.get("payload") timeout = int(body.get("timeout") or 60) if not url: self._respond(400, {"error": "missing url"}) return print(f" → {method} {url}") try: if method == "POST": r = requests.post(url, json=payload, timeout=timeout) else: r = requests.get(url, timeout=timeout) try: resp_body = r.json() except Exception: resp_body = r.text print(f" ← HTTP {r.status_code}") self._respond(200, {"status": r.status_code, "body": resp_body}) except requests.exceptions.Timeout: print(" ← timeout") self._respond(504, {"error": "request timeout", "status": 504}) except Exception as exc: print(f" ← error: {exc}") self._respond(500, {"error": str(exc), "status": 0}) def _respond(self, code, data): out = json.dumps(data, ensure_ascii=False).encode("utf-8") self.send_response(code) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(out))) self._cors() self.end_headers() self.wfile.write(out) def log_message(self, fmt, *args): pass # 由 do_POST / do_GET 自行輸出 def main(): parser = argparse.ArgumentParser(description="本機 POS Proxy") parser.add_argument("--port", type=int, default=18080, help="監聽 port(預設 18080)") parser.add_argument("--host", default="127.0.0.1", help="監聽位址(預設 127.0.0.1)") args = parser.parse_args() server = HTTPServer((args.host, args.port), PosProxyHandler) addr = f"http://{args.host}:{args.port}" print(f"[POS Proxy] 啟動於 {addr}") print(f"[POS Proxy] 健康檢查:{addr}/health") print(f"[POS Proxy] 按 Ctrl+C 停止\n") try: server.serve_forever() except KeyboardInterrupt: print("\n[POS Proxy] 已停止") if __name__ == "__main__": main()