InfoLens / backend /api /static.py
dqy08's picture
访问统计持久化;修复LMF高度回退问题
c4753aa
"""静态文件路由"""
import mimetypes
from pathlib import Path
from urllib.parse import unquote
from flask import Response, redirect, abort, request
from werkzeug.utils import safe_join
from backend.access_log import log_cached_demo, log_json_demo, log_page_load
def _read_static_file(directory: str, path: str) -> Response:
"""读取静态文件并返回 Response,避免 send_from_directory 在 ASGI/a2wsgi 下
流式传输导致的 Content-Length 不匹配(RuntimeError: Response content shorter than Content-Length)。
"""
base = Path(directory).resolve()
safe_path = safe_join(str(base), path)
if safe_path is None:
abort(404)
full_path = Path(safe_path)
if not full_path.is_file() or not str(full_path.resolve()).startswith(str(base)):
abort(404)
content = full_path.read_bytes()
mimetype, _ = mimetypes.guess_type(path)
mimetype = mimetype or "application/octet-stream"
return Response(content, mimetype=mimetype, headers={"Content-Length": str(len(content))})
def register_static_routes(app):
"""注册静态文件路由"""
@app.route('/')
def redir():
target = 'client/index.html'
if request.query_string:
target += '?' + request.query_string.decode()
return redirect(target)
@app.route('/client/<path:path>')
def send_static(path):
"""serves all files from ./client/dist/ to ``/client/<path:path>``"""
if path.endswith('.html'):
log_page_load(path)
if path.endswith('.json'):
log_cached_demo(path)
return _read_static_file('client/dist', path)
@app.route('/demo/<path:path>')
def send_demo(path):
"""serves all demo files from the demo dir to ``/demo/<path:path>``"""
from backend.app_context import get_data_dir
data_dir = get_data_dir()
log_json_demo(path)
try:
decoded_path = unquote(path)
return _read_static_file(str(data_dir), decoded_path)
except Exception:
try:
return _read_static_file(str(data_dir), path)
except Exception:
abort(404)