fix: serve frontend js with javascript mime (#1808)

This commit is contained in:
zhulinsen
2026-06-27 17:09:06 +08:00
committed by GitHub
parent 688e980714
commit ebf340a519
3 changed files with 49 additions and 3 deletions

View File

@@ -42,7 +42,12 @@ _INDEX_ASSET_REF_PATTERN = re.compile(
r"""(?:src|href)\s*=\s*["'](/assets/[^"']+)["']""",
re.IGNORECASE,
)
_SAFE_MISSING_ASSET_MEDIA_TYPES = frozenset({"text/css", "text/javascript"})
_FRONTEND_ASSET_MEDIA_TYPES = {
".css": "text/css",
".js": "text/javascript",
".mjs": "text/javascript",
}
_SAFE_MISSING_ASSET_MEDIA_TYPES = frozenset(_FRONTEND_ASSET_MEDIA_TYPES.values())
_FRONTEND_INDEX_NO_CACHE_HEADERS = {
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
"Pragma": "no-cache",
@@ -116,9 +121,23 @@ def _resolve_asset_path(assets_dir: Path, asset_path: str) -> Optional[Path]:
return candidate
def _register_frontend_asset_mime_types() -> None:
"""Keep Vite module assets loadable even when OS MIME maps are wrong."""
for suffix, media_type in _FRONTEND_ASSET_MEDIA_TYPES.items():
mimetypes.add_type(media_type, suffix)
def _frontend_asset_media_type(asset_path: str) -> Optional[str]:
suffix = Path(asset_path).suffix.lower()
if suffix in _FRONTEND_ASSET_MEDIA_TYPES:
return _FRONTEND_ASSET_MEDIA_TYPES[suffix]
content_type, _ = mimetypes.guess_type(asset_path)
return content_type
def _missing_asset_media_type(asset_path: str) -> str:
"""Return a safe media type for a missing asset response."""
content_type, _ = mimetypes.guess_type(asset_path)
content_type = _frontend_asset_media_type(asset_path)
if content_type in _SAFE_MISSING_ASSET_MEDIA_TYPES:
return content_type
return "text/plain"
@@ -286,6 +305,8 @@ def create_app(static_dir: Optional[Path] = None) -> FastAPI:
配置完成的 FastAPI 应用实例
"""
# 默认静态文件目录
_register_frontend_asset_mime_types()
if static_dir is None:
static_dir = Path(__file__).parent.parent / "static"
@@ -516,7 +537,7 @@ def create_app(static_dir: Optional[Path] = None) -> FastAPI:
return _frontend_index_response(static_dir)
# Issue #520: Explicitly resolve MIME type to avoid
# browsers rejecting JS modules served as text/plain.
content_type, _ = mimetypes.guess_type(str(file_path))
content_type = _frontend_asset_media_type(str(file_path))
return FileResponse(file_path, media_type=content_type)
return _frontend_index_response(static_dir)

View File

@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- [修复] 将 Docker 可安装的 Longbridge SDK 版本固定为 0.2.75,避免 `longbridge>=0.2.77` 从包索引消失后导致 docker-build 失败。
- [修复] 默认通知报告补充展示 `dashboard.phase_decision` 盘中决策护栏字段,避免与模板渲染路径展示不一致。
- [修复] 修复 Windows 环境下 Web/Desktop 静态 JS 资源可能被识别为 `text/plain` 导致前端黑屏的问题。
- [改进] Web 设置页新增首次启动配置检查卡,串联基础配置状态、自选股入口、模型配置入口和一次简短试跑。
- [改进] 通知报告的分析结果摘要不再展开 AI 决策信号明细,完整信号保留在个股详情和单股报告中。
- [新功能] #1595 P1.5 新增 Provider Cache Capability Registry按 provider、api surface、gateway 和 verification status 建模 prompt cache 能力,未知 OpenAI-compatible route 默认 telemetry only。

View File

@@ -14,6 +14,7 @@ from __future__ import annotations
import importlib
import json
import logging
import mimetypes
import os
import sys
from pathlib import Path
@@ -205,6 +206,29 @@ def test_existing_asset_is_served_from_explicit_assets_route(tmp_path: Path) ->
assert css_response.headers["content-type"].startswith("text/css")
def test_existing_js_asset_overrides_bad_system_mime_mapping(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
from api.app import create_app
monkeypatch.setitem(mimetypes.types_map, ".js", "text/plain")
monkeypatch.setitem(mimetypes.common_types, ".js", "text/plain")
static_dir = tmp_path / "static"
assets_dir = static_dir / "assets"
assets_dir.mkdir(parents=True)
js_file = assets_dir / "index-abc.js"
js_file.write_text("console.log('ok')", encoding="utf-8")
(assets_dir / "index-abc.css").write_text("body{color:#fff}", encoding="utf-8")
_write_index(static_dir, _vite_index("index-abc.js", "index-abc.css"))
client = TestClient(create_app(static_dir=static_dir))
js_response = client.get("/assets/index-abc.js")
assert js_response.status_code == 200
assert js_response.text == "console.log('ok')"
assert js_response.headers["content-type"].startswith("text/javascript")
def test_existing_asset_supports_head_and_conditional_requests(tmp_path: Path) -> None:
from api.app import create_app