Обновление файла

This commit is contained in:
2026-07-25 01:42:03 +03:00
parent cb1bbcb24b
commit c8950e19e6
+37 -10
View File
@@ -7,11 +7,12 @@
- Cookie PVEAuthCookie — если заданы PVE_USERNAME + PVE_PASSWORD
3. Проксирует бинарный VNC-поток между браузером клиента и Proxmox.
Используется aiohttp вместо websockets, потому что:
- Proxmox 8.x+ делает 302 Redirect с Location со схемой https:// вместо wss://,
а websockets на uvloop не умеет правильно следовать за такими редиректами
(raise InvalidURI "scheme isn't ws or wss").
- aiohttp корректно обрабатывает HTTP→WS-редиректы с заменой scheme.
Особенности Proxmox 8.x+:
- При WebSocket-handshake на vncwebsocket Proxmox отвечает 302 Redirect
на тот же URL, но со схемой https:// вместо wss://. aiohttp при
перенаправлении теряет Cookie-заголовок (или не может следовать за
редиректом без TLS-валидации). Поэтому делаем редирект вручную:
перехватываем ответ, переписываем scheme, открываем WS сами.
"""
import asyncio
@@ -76,7 +77,6 @@ async def console_ws(
guest_path = "qemu" if guest_type == "vm" else "lxc"
pve_host_only = settings.pve_host.replace("http://", "").replace("https://", "")
# Экранируем спецсимволы в тикете/порте (vncticket может содержать # / % :).
safe_ticket = quote(ticket, safe="")
safe_port = quote(str(port), safe="")
upstream_url = (
@@ -84,8 +84,6 @@ async def console_ws(
f"?port={safe_port}&vncticket={safe_ticket}"
)
# Определяем способ аутентификации: cookie приоритетнее (некоторые PVE
# не принимают API-токен на WebSocket).
headers: dict = {}
auth = await _get_pve_auth_cookie()
if auth:
@@ -106,9 +104,38 @@ async def console_ws(
upstream: Optional[aiohttp.ClientWebSocketResponse] = None
session: Optional[ClientSession] = None
try:
# aiohttp сам следует за HTTP-редиректами и при этом корректно
# меняет схему с https на wss (что websockets на uvloop не делает).
session = ClientSession()
# Отключаем автоматический redirect, чтобы самим обработать https→wss
# с сохранением Cookie-заголовка.
resp = await session.get(
upstream_url,
headers=headers,
allow_redirects=False,
ssl=False if not settings.pve_verify_ssl else None,
)
if resp.status in (301, 302, 303, 307, 308):
location = resp.headers.get("Location", "")
logger.info("console_ws: redirect to %s", location)
if location.startswith("https://"):
upstream_url = "wss://" + location[len("https://"):]
elif location.startswith("http://"):
upstream_url = "ws://" + location[len("http://"):]
else:
upstream_url = location
await resp.release()
elif resp.status == 101:
# Proxmox уже сделал WS-handshake (редкий случай без редиректа).
await resp.release()
else:
await resp.release()
raise aiohttp.ClientResponseError(
request_info=resp.request_info,
history=resp.history,
status=resp.status,
message=f"unexpected status {resp.status}",
headers=resp.headers,
)
upstream = await session.ws_connect(
upstream_url,
headers=headers,