Skip to content

Commit 0e68288

Browse files
孙圣翔²⁰₂₁孙圣翔²⁰₂₁
孙圣翔²⁰₂₁
authored and
孙圣翔²⁰₂₁
committed
fix中文路径支持不好的问题
1 parent 28a719f commit 0e68288

File tree

4 files changed

+46
-16
lines changed

4 files changed

+46
-16
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
dev:
2-
npx nodemon -e "js html py" --exec "uvicorn servefs.main:app --port 7001"
2+
npx nodemon -e "js html py" --exec "poetry run servefs --port 7001 -d files"
33

44
auth:
55
npx nodemon -e "js html py" --exec "poetry run servefs -b foo:123 -d ./files --port 7001"

servefs/routes/page.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1+
import datetime
2+
import logging
13
import mimetypes
24
import os
5+
import urllib.parse
6+
from email.utils import format_datetime
37
from pathlib import Path
4-
from typing import AsyncIterator, Union
8+
from typing import AsyncIterator, Optional, Union
59

610
import aiofiles
711
from fastapi import APIRouter, HTTPException, Request
812
from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
913

1014
from ..utils.static import ETaggedStaticFiles
1115

16+
logger = logging.getLogger(__name__)
17+
1218
# Get current module path
1319
PACKAGE_DIR = Path(__file__).parent.parent
1420

@@ -27,9 +33,28 @@ async def stream_file_range(file_path: Path, start: int, end: int) -> AsyncItera
2733
bytes_remaining -= len(chunk)
2834
yield chunk
2935

36+
def get_content_disposition_header(filename: str, as_attachment: bool = False) -> str:
37+
"""Generate Content-Disposition header value with RFC 5987 compatible filename encoding
38+
39+
Args:
40+
filename: The filename to encode
41+
as_attachment: Whether to use attachment or inline disposition
42+
43+
Returns:
44+
str: The properly encoded Content-Disposition header value
45+
"""
46+
disposition_type = "attachment" if as_attachment else "inline"
47+
try:
48+
filename.encode('ascii')
49+
return f'{disposition_type}; filename="{filename}"'
50+
except UnicodeEncodeError:
51+
encoded_filename = urllib.parse.quote(filename)
52+
return f'{disposition_type}; filename*=UTF-8\'\'{encoded_filename}'
53+
54+
3055
async def handle_file_request(
3156
file_path: Path,
32-
range_header: str = None,
57+
range_header: Optional[str] = None,
3358
as_attachment: bool = False
3459
) -> Union[FileResponse, StreamingResponse]:
3560
"""处理文件请求的通用函数"""
@@ -46,11 +71,13 @@ async def handle_file_request(
4671

4772
# 如果没有 Range 头,直接返回完整文件
4873
if not range_header:
49-
headers = {}
50-
if as_attachment:
51-
headers["Content-Disposition"] = f'attachment; filename="{file_path.name}"'
52-
else:
53-
headers["Content-Disposition"] = f'inline; filename="{file_path.name}"'
74+
headers = {
75+
"Last-Modified": format_datetime(datetime.datetime.fromtimestamp(os.path.getmtime(file_path))),
76+
"Accept-Ranges": "bytes",
77+
"Content-Length": str(file_size),
78+
}
79+
80+
headers["Content-Disposition"] = get_content_disposition_header(file_path.name, as_attachment)
5481

5582
return FileResponse(
5683
path=file_path,
@@ -66,13 +93,14 @@ async def handle_file_request(
6693

6794
if start >= file_size:
6895
raise HTTPException(status_code=416, detail="Range not satisfiable")
69-
96+
7097
headers = {
7198
"Content-Range": f"bytes {start}-{end}/{file_size}",
7299
"Accept-Ranges": "bytes",
73100
"Content-Length": str(end - start + 1),
74101
"Content-Type": mime_type,
75-
"Content-Disposition": "attachment" if as_attachment else "inline" + f'; filename="{file_path.name}"'
102+
"Content-Disposition": get_content_disposition_header(file_path.name, as_attachment),
103+
"Last-Modified": format_datetime(datetime.datetime.fromtimestamp(os.path.getmtime(file_path)))
76104
}
77105

78106
return StreamingResponse(
@@ -83,8 +111,10 @@ async def handle_file_request(
83111

84112
except (ValueError, IndexError):
85113
raise HTTPException(status_code=416, detail="Invalid range header")
86-
87114
except Exception as e:
115+
logger.exception("Error processing file request")
116+
if isinstance(e, HTTPException):
117+
raise e
88118
raise HTTPException(status_code=500, detail=str(e))
89119

90120
# Mount static files for direct access to static assets
@@ -108,9 +138,9 @@ async def serve_blob_path(path: str):
108138
async def get_raw_file(file_path: str, request: Request):
109139
"""Get raw file content with Range support"""
110140
try:
111-
file_path = request.app.state.ROOT_DIR / file_path
141+
full_path: Path = request.app.state.ROOT_DIR / file_path
112142
return await handle_file_request(
113-
file_path=file_path,
143+
file_path=full_path,
114144
range_header=request.headers.get("range"),
115145
as_attachment=False
116146
)
@@ -123,9 +153,9 @@ async def get_raw_file(file_path: str, request: Request):
123153
async def download_file(file_path: str, request: Request):
124154
"""Download file with Range support"""
125155
try:
126-
file_path = request.app.state.ROOT_DIR / file_path
156+
full_path: Path = request.app.state.ROOT_DIR / file_path
127157
return await handle_file_request(
128-
file_path=file_path,
158+
file_path=full_path,
129159
range_header=request.headers.get("range"),
130160
as_attachment=True
131161
)

servefs/static/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ <h1>ServeFS File Browser</h1>
263263
</el-dialog>
264264
</div>
265265

266-
<script src="/static/js/main.js"></script>
266+
<script src="/static/js/index.js"></script>
267267

268268
</body>
269269
</html>
File renamed without changes.

0 commit comments

Comments
 (0)