Commit bb6420d3b834971dd2ee63bddab769933014ddd4
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 | 197 | <p>saas-search © 2025 | API: <span id="apiUrl">Loading...</span></p> |
| 198 | 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 | 202 | <script> |
| 203 | 203 | // 自动补全功能(使用后端 /search/suggestions 接口) |
| 204 | 204 | const SUGGEST_API = API_BASE_URL + '/search/suggestions'; | ... | ... |
scripts/frontend_server.py
| ... | ... | @@ -19,6 +19,10 @@ from dotenv import load_dotenv |
| 19 | 19 | project_root = Path(__file__).parent.parent |
| 20 | 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 | 26 | # Backend proxy target for same-origin API forwarding |
| 23 | 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__), '../frontend') |
| 27 | 31 | os.chdir(frontend_dir) |
| 28 | 32 | |
| 29 | 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 | 36 | # Configure logging to suppress scanner noise |
| 33 | 37 | logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s') |
| ... | ... | @@ -116,17 +120,48 @@ class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler, RateLimitingMix |
| 116 | 120 | self.wfile.write(b'{"error":"Bad Gateway: backend proxy failed"}') |
| 117 | 121 | |
| 118 | 122 | def do_GET(self): |
| 119 | - """Handle GET requests with lightweight API proxy.""" | |
| 123 | + """Handle GET requests with API config injection.""" | |
| 120 | 124 | path = self.path.split('?')[0] |
| 121 | 125 | |
| 122 | 126 | # Proxy API paths to backend first |
| 123 | 127 | if self._is_proxy_path(path): |
| 124 | 128 | self._proxy_to_backend() |
| 125 | 129 | return |
| 130 | + | |
| 126 | 131 | # Route / to index.html |
| 127 | 132 | if path == '/' or path == '': |
| 128 | 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 | 166 | def do_POST(self): |
| 132 | 167 | """Handle POST requests. Proxy API requests to backend.""" | ... | ... |