Fix Reader proxy and route state bugs
Browse files- app.py +17 -6
- static/app.js +4 -4
app.py
CHANGED
|
@@ -153,8 +153,7 @@ async def get_reader_assets() -> dict:
|
|
| 153 |
reader_assets_cache_at = now
|
| 154 |
except Exception as exc:
|
| 155 |
print(f"reader assets refresh failed: {exc}")
|
| 156 |
-
|
| 157 |
-
reader_assets_cache_at = now
|
| 158 |
finally:
|
| 159 |
if response is not None:
|
| 160 |
response.release()
|
|
@@ -1458,11 +1457,12 @@ async def api_reader_content(request: Request, url: str = Query(...)):
|
|
| 1458 |
return JSONResponse({"error": str(exc)}, status_code=403)
|
| 1459 |
forwarded_headers = {
|
| 1460 |
name: request.headers[name]
|
| 1461 |
-
for name in ("Range", "If-Range")
|
| 1462 |
if name in request.headers
|
| 1463 |
}
|
| 1464 |
semaphore = app.state.upstream_semaphore
|
| 1465 |
await semaphore.acquire()
|
|
|
|
| 1466 |
try:
|
| 1467 |
upstream = await open_download_response(
|
| 1468 |
app.state.http_session,
|
|
@@ -1471,10 +1471,14 @@ async def api_reader_content(request: Request, url: str = Query(...)):
|
|
| 1471 |
method=request.method,
|
| 1472 |
request_headers=forwarded_headers,
|
| 1473 |
)
|
|
|
|
|
|
|
| 1474 |
except Exception as exc:
|
| 1475 |
-
semaphore.release()
|
| 1476 |
print(f"阅读代理异常: {exc}")
|
| 1477 |
return JSONResponse({"error": str(exc) or "上游阅读文件加载失败"}, status_code=502)
|
|
|
|
|
|
|
|
|
|
| 1478 |
headers = {name: upstream.headers[name] for name in READER_UPSTREAM_RESPONSE_HEADERS if name in upstream.headers}
|
| 1479 |
headers.setdefault("Cache-Control", "public, max-age=300")
|
| 1480 |
headers["Content-Encoding"] = "identity"
|
|
@@ -1499,6 +1503,7 @@ async def api_reader_content(request: Request, url: str = Query(...)):
|
|
| 1499 |
finally:
|
| 1500 |
semaphore.release()
|
| 1501 |
|
|
|
|
| 1502 |
return StreamingResponse(stream_reader_content(), status_code=status, headers=headers)
|
| 1503 |
|
| 1504 |
|
|
@@ -1513,16 +1518,21 @@ async def api_download(file: str = Query(...), link: str = Query(...)):
|
|
| 1513 |
session = app.state.http_session
|
| 1514 |
semaphore = app.state.upstream_semaphore
|
| 1515 |
await semaphore.acquire()
|
|
|
|
| 1516 |
try:
|
| 1517 |
upstream = await open_download_response(
|
| 1518 |
session,
|
| 1519 |
target_url,
|
| 1520 |
aiohttp.ClientTimeout(total=120, connect=15, sock_read=30),
|
| 1521 |
)
|
|
|
|
|
|
|
| 1522 |
except Exception as e:
|
| 1523 |
-
semaphore.release()
|
| 1524 |
print(f"下载代理异常: {e}")
|
| 1525 |
return JSONResponse({"error": str(e) or "上游下载失败"}, status_code=502)
|
|
|
|
|
|
|
|
|
|
| 1526 |
if upstream.status != 200:
|
| 1527 |
status = upstream.status
|
| 1528 |
upstream.release()
|
|
@@ -1541,6 +1551,7 @@ async def api_download(file: str = Query(...), link: str = Query(...)):
|
|
| 1541 |
raise
|
| 1542 |
finally:
|
| 1543 |
semaphore.release()
|
|
|
|
| 1544 |
return StreamingResponse(
|
| 1545 |
stream_response(),
|
| 1546 |
media_type="application/octet-stream",
|
|
@@ -1712,7 +1723,7 @@ async def serve_spa(rest_of_path: str):
|
|
| 1712 |
if index_path.exists():
|
| 1713 |
repo_short = rest_of_path.strip("/").split("/")[0] if rest_of_path.strip("/") else None
|
| 1714 |
if repo_short and f"VoiceOfML/{repo_short}" not in repo_counts:
|
| 1715 |
-
|
| 1716 |
cache_key = f"VoiceOfML/{repo_short}" if repo_short else None
|
| 1717 |
html = injected_html_cache.get(cache_key)
|
| 1718 |
if html is not None:
|
|
|
|
| 153 |
reader_assets_cache_at = now
|
| 154 |
except Exception as exc:
|
| 155 |
print(f"reader assets refresh failed: {exc}")
|
| 156 |
+
reader_assets_cache_at = now
|
|
|
|
| 157 |
finally:
|
| 158 |
if response is not None:
|
| 159 |
response.release()
|
|
|
|
| 1457 |
return JSONResponse({"error": str(exc)}, status_code=403)
|
| 1458 |
forwarded_headers = {
|
| 1459 |
name: request.headers[name]
|
| 1460 |
+
for name in ("Range", "If-Range", "If-None-Match", "If-Modified-Since")
|
| 1461 |
if name in request.headers
|
| 1462 |
}
|
| 1463 |
semaphore = app.state.upstream_semaphore
|
| 1464 |
await semaphore.acquire()
|
| 1465 |
+
release_semaphore = True
|
| 1466 |
try:
|
| 1467 |
upstream = await open_download_response(
|
| 1468 |
app.state.http_session,
|
|
|
|
| 1471 |
method=request.method,
|
| 1472 |
request_headers=forwarded_headers,
|
| 1473 |
)
|
| 1474 |
+
except asyncio.CancelledError:
|
| 1475 |
+
raise
|
| 1476 |
except Exception as exc:
|
|
|
|
| 1477 |
print(f"阅读代理异常: {exc}")
|
| 1478 |
return JSONResponse({"error": str(exc) or "上游阅读文件加载失败"}, status_code=502)
|
| 1479 |
+
finally:
|
| 1480 |
+
if release_semaphore and "upstream" not in locals():
|
| 1481 |
+
semaphore.release()
|
| 1482 |
headers = {name: upstream.headers[name] for name in READER_UPSTREAM_RESPONSE_HEADERS if name in upstream.headers}
|
| 1483 |
headers.setdefault("Cache-Control", "public, max-age=300")
|
| 1484 |
headers["Content-Encoding"] = "identity"
|
|
|
|
| 1503 |
finally:
|
| 1504 |
semaphore.release()
|
| 1505 |
|
| 1506 |
+
release_semaphore = False
|
| 1507 |
return StreamingResponse(stream_reader_content(), status_code=status, headers=headers)
|
| 1508 |
|
| 1509 |
|
|
|
|
| 1518 |
session = app.state.http_session
|
| 1519 |
semaphore = app.state.upstream_semaphore
|
| 1520 |
await semaphore.acquire()
|
| 1521 |
+
release_semaphore = True
|
| 1522 |
try:
|
| 1523 |
upstream = await open_download_response(
|
| 1524 |
session,
|
| 1525 |
target_url,
|
| 1526 |
aiohttp.ClientTimeout(total=120, connect=15, sock_read=30),
|
| 1527 |
)
|
| 1528 |
+
except asyncio.CancelledError:
|
| 1529 |
+
raise
|
| 1530 |
except Exception as e:
|
|
|
|
| 1531 |
print(f"下载代理异常: {e}")
|
| 1532 |
return JSONResponse({"error": str(e) or "上游下载失败"}, status_code=502)
|
| 1533 |
+
finally:
|
| 1534 |
+
if release_semaphore and "upstream" not in locals():
|
| 1535 |
+
semaphore.release()
|
| 1536 |
if upstream.status != 200:
|
| 1537 |
status = upstream.status
|
| 1538 |
upstream.release()
|
|
|
|
| 1551 |
raise
|
| 1552 |
finally:
|
| 1553 |
semaphore.release()
|
| 1554 |
+
release_semaphore = False
|
| 1555 |
return StreamingResponse(
|
| 1556 |
stream_response(),
|
| 1557 |
media_type="application/octet-stream",
|
|
|
|
| 1723 |
if index_path.exists():
|
| 1724 |
repo_short = rest_of_path.strip("/").split("/")[0] if rest_of_path.strip("/") else None
|
| 1725 |
if repo_short and f"VoiceOfML/{repo_short}" not in repo_counts:
|
| 1726 |
+
return PlainTextResponse("Repository not found", status_code=404)
|
| 1727 |
cache_key = f"VoiceOfML/{repo_short}" if repo_short else None
|
| 1728 |
html = injected_html_cache.get(cache_key)
|
| 1729 |
if html is not None:
|
static/app.js
CHANGED
|
@@ -1082,6 +1082,10 @@ function syncStateToURL(replace = true) {
|
|
| 1082 |
if (STATE.mode !== "global" && STATE.browserPath) {
|
| 1083 |
params.set("path", STATE.browserPath);
|
| 1084 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1085 |
if (DOM.leftSidebar.classList.contains("expanded-wide")) {
|
| 1086 |
params.set("wide", "1");
|
| 1087 |
}
|
|
@@ -1158,10 +1162,6 @@ function syncURLToState() {
|
|
| 1158 |
STATE.filterFolderSelfs = urlSelfs;
|
| 1159 |
STATE.filterFolderSubtrees = urlSubtrees;
|
| 1160 |
STATE.filterFolders = mergeFolderFilters(STATE.filterFolderSelfs, STATE.filterFolderSubtrees);
|
| 1161 |
-
params.delete("folder_self");
|
| 1162 |
-
params.delete("folder_subtree");
|
| 1163 |
-
const cleanUrl = params.toString() ? `${window.location.pathname}?${params.toString()}` : window.location.pathname;
|
| 1164 |
-
history.replaceState(null, "", cleanUrl);
|
| 1165 |
} else {
|
| 1166 |
STATE.filterFolderSelfs = [];
|
| 1167 |
STATE.filterFolderSubtrees = [];
|
|
|
|
| 1082 |
if (STATE.mode !== "global" && STATE.browserPath) {
|
| 1083 |
params.set("path", STATE.browserPath);
|
| 1084 |
}
|
| 1085 |
+
if (STATE.mode !== "global") {
|
| 1086 |
+
STATE.filterFolderSelfs.forEach(folder => params.append("folder_self", folder));
|
| 1087 |
+
STATE.filterFolderSubtrees.forEach(folder => params.append("folder_subtree", folder));
|
| 1088 |
+
}
|
| 1089 |
if (DOM.leftSidebar.classList.contains("expanded-wide")) {
|
| 1090 |
params.set("wide", "1");
|
| 1091 |
}
|
|
|
|
| 1162 |
STATE.filterFolderSelfs = urlSelfs;
|
| 1163 |
STATE.filterFolderSubtrees = urlSubtrees;
|
| 1164 |
STATE.filterFolders = mergeFolderFilters(STATE.filterFolderSelfs, STATE.filterFolderSubtrees);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1165 |
} else {
|
| 1166 |
STATE.filterFolderSelfs = [];
|
| 1167 |
STATE.filterFolderSubtrees = [];
|