Commit bb6420d3b834971dd2ee63bddab769933014ddd4

Authored by tangwang
1 parent 7fbca0d7

前端同源代理后端,避免写死6002和外部认证冲突

- 前端 JS 不再写死后端地址:默认 API_BASE_URL 为空串,所有搜索与 suggest 请求改为同源路径 (/search/*),仅在显式注入 window.API_BASE_URL 时才覆盖,避免 .env 中旧的 http://43.166.252.75:6002 等配置污染浏览器请求。
- 在 scripts/frontend_server.py 上实现轻量级反向代理:拦截 /search/、/admin/、/indexer/ 的 GET/POST/OPTIONS 请求,服务端将请求转发到本机 6002 (BACKEND_PROXY_URL,默认 http://127.0.0.1:6002),并把响应原样返回前端。
- 通过“浏览器 → web服务器:6003(认证) → GPU:6003(本项目前端) → GPU 本机:6002(后端)”这条链路,彻底绕开 web 服务器 6002 上单独的 Basic Auth,解决了外网访问时前端能打开但搜索请求被 web:6002 拦截的问题。
- frontend_server 默认不再注入 window.API_BASE_URL,只有在设置 FRONTEND_INJECT_API_BASE_URL=1 且 API_BASE_URL 有值时才向 HTML 注入脚本,确保默认行为始终是同源调用,由 6003 统一代理后端。
- 更新 frontend/index.html 中的静态 JS 版本号(tenant_facets_config.js 和 app.js),强制浏览器拉取最新脚本,避免旧版前端继续使用硬编码的后端地址。

Made-with: Cursor
Showing 2 changed files with 40 additions and 5 deletions   Show diff stats
frontend/index.html
@@ -197,8 +197,8 @@ @@ -197,8 +197,8 @@
197 <p>saas-search © 2025 | API: <span id="apiUrl">Loading...</span></p> 197 <p>saas-search © 2025 | API: <span id="apiUrl">Loading...</span></p>
198 </footer> 198 </footer>
199 199
200 - <script src="/static/js/tenant_facets_config.js?v=1.3"></script>  
201 - <script src="/static/js/app.js?v=3.6"></script> 200 + <script src="/static/js/tenant_facets_config.js?v=1.4"></script>
  201 + <script src="/static/js/app.js?v=1.0"></script>
202 <script> 202 <script>
203 // 自动补全功能(使用后端 /search/suggestions 接口) 203 // 自动补全功能(使用后端 /search/suggestions 接口)
204 const SUGGEST_API = API_BASE_URL + '/search/suggestions'; 204 const SUGGEST_API = API_BASE_URL + '/search/suggestions';
scripts/frontend_server.py
@@ -19,6 +19,10 @@ from dotenv import load_dotenv @@ -19,6 +19,10 @@ from dotenv import load_dotenv
19 project_root = Path(__file__).parent.parent 19 project_root = Path(__file__).parent.parent
20 load_dotenv(project_root / '.env') 20 load_dotenv(project_root / '.env')
21 21
  22 +# Get API_BASE_URL from environment(默认不注入,避免被旧 .env 覆盖同源策略)
  23 +# 仅当显式设置 FRONTEND_INJECT_API_BASE_URL=1 时才注入 window.API_BASE_URL。
  24 +API_BASE_URL = os.getenv('API_BASE_URL') or None
  25 +INJECT_API_BASE_URL = os.getenv('FRONTEND_INJECT_API_BASE_URL', '0') == '1'
22 # Backend proxy target for same-origin API forwarding 26 # Backend proxy target for same-origin API forwarding
23 BACKEND_PROXY_URL = os.getenv('BACKEND_PROXY_URL', 'http://127.0.0.1:6002').rstrip('/') 27 BACKEND_PROXY_URL = os.getenv('BACKEND_PROXY_URL', 'http://127.0.0.1:6002').rstrip('/')
24 28
@@ -27,7 +31,7 @@ frontend_dir = os.path.join(os.path.dirname(__file__), &#39;../frontend&#39;) @@ -27,7 +31,7 @@ frontend_dir = os.path.join(os.path.dirname(__file__), &#39;../frontend&#39;)
27 os.chdir(frontend_dir) 31 os.chdir(frontend_dir)
28 32
29 # Get port from environment variable or default 33 # Get port from environment variable or default
30 -PORT = int(os.getenv('FRONTEND_PORT', 6003)) 34 +PORT = int(os.getenv('PORT', 6003))
31 35
32 # Configure logging to suppress scanner noise 36 # Configure logging to suppress scanner noise
33 logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s') 37 logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -116,17 +120,48 @@ class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler, RateLimitingMix @@ -116,17 +120,48 @@ class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler, RateLimitingMix
116 self.wfile.write(b'{"error":"Bad Gateway: backend proxy failed"}') 120 self.wfile.write(b'{"error":"Bad Gateway: backend proxy failed"}')
117 121
118 def do_GET(self): 122 def do_GET(self):
119 - """Handle GET requests with lightweight API proxy.""" 123 + """Handle GET requests with API config injection."""
120 path = self.path.split('?')[0] 124 path = self.path.split('?')[0]
121 125
122 # Proxy API paths to backend first 126 # Proxy API paths to backend first
123 if self._is_proxy_path(path): 127 if self._is_proxy_path(path):
124 self._proxy_to_backend() 128 self._proxy_to_backend()
125 return 129 return
  130 +
126 # Route / to index.html 131 # Route / to index.html
127 if path == '/' or path == '': 132 if path == '/' or path == '':
128 self.path = '/index.html' + (self.path.split('?', 1)[1] if '?' in self.path else '') 133 self.path = '/index.html' + (self.path.split('?', 1)[1] if '?' in self.path else '')
129 - super().do_GET() 134 +
  135 + # Inject API config for HTML files
  136 + if self.path.endswith('.html'):
  137 + self._serve_html_with_config()
  138 + else:
  139 + super().do_GET()
  140 +
  141 + def _serve_html_with_config(self):
  142 + """Serve HTML with optional API_BASE_URL injected."""
  143 + try:
  144 + file_path = self.path.lstrip('/')
  145 + if not os.path.exists(file_path):
  146 + self.send_error(404)
  147 + return
  148 +
  149 + with open(file_path, 'r', encoding='utf-8') as f:
  150 + html = f.read()
  151 +
  152 + # 默认不注入 API_BASE_URL,避免历史 .env(如 http://xx:6002)覆盖同源调用。
  153 + # 仅当 FRONTEND_INJECT_API_BASE_URL=1 且 API_BASE_URL 有值时才注入。
  154 + if INJECT_API_BASE_URL and API_BASE_URL:
  155 + config_script = f'<script>window.API_BASE_URL="{API_BASE_URL}";</script>\n '
  156 + html = html.replace('<script src="/static/js/app.js', config_script + '<script src="/static/js/app.js', 1)
  157 +
  158 + self.send_response(200)
  159 + self.send_header('Content-Type', 'text/html; charset=utf-8')
  160 + self.end_headers()
  161 + self.wfile.write(html.encode('utf-8'))
  162 + except Exception as e:
  163 + logging.error(f"Error serving HTML: {e}")
  164 + self.send_error(500)
130 165
131 def do_POST(self): 166 def do_POST(self):
132 """Handle POST requests. Proxy API requests to backend.""" 167 """Handle POST requests. Proxy API requests to backend."""