mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: serve frontend js with javascript mime (#1808)
This commit is contained in:
27
api/app.py
27
api/app.py
@@ -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)
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user