Add retry logic and timeout to media proxy

This commit is contained in:
Zed 2026-06-19 22:00:28 +02:00
commit 581c6f1714

View file

@ -1,6 +1,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
import uri, strutils, httpclient, os, hashes, base64, re import uri, strutils, httpclient, os, hashes, base64, re
import asynchttpserver, asyncstreams, asyncfile, asyncnet import asynchttpserver, asyncstreams, asyncfile, asyncnet
import asyncdispatch
import jester import jester
@ -32,17 +33,33 @@ template respond*(req: asynchttpserver.Request; headers) =
proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} = proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} =
result = Http200 result = Http200
let let request = req.getNativeReq()
request = req.getNativeReq()
client = newAsyncHttpClient(maxRedirects = 0)
for attempt in 0 .. 2:
let client = newAsyncHttpClient(maxRedirects = 0)
var shouldRetry = false
try: try:
let res = await client.get(url) let resFut = client.get(url)
let completed = await withTimeout(resFut, 5000)
if not completed:
if attempt < 2:
echo "[media] Retry $1/2, timeout after 5s, url: $2" % [$(attempt + 1), url]
shouldRetry = true
else:
echo "[media] Proxying timeout after 5s, url: $1" % [url]
return Http504
else:
let res = resFut.read()
if res.status != "200 OK": if res.status != "200 OK":
if res.status != "404 Not Found": if res.status == "404 Not Found":
return Http404
if attempt < 2:
echo "[media] Retry $1/2, status: $2, url: $3" % [$(attempt + 1), res.status, url]
shouldRetry = true
else:
echo "[media] Proxying failed, status: $1, url: $2" % [res.status, url] echo "[media] Proxying failed, status: $1, url: $2" % [res.status, url]
return Http404 return Http404
else:
let hashed = $hash(url) let hashed = $hash(url)
if request.headers.getOrDefault("If-None-Match") == hashed: if request.headers.getOrDefault("If-None-Match") == hashed:
return Http304 return Http304
@ -68,11 +85,18 @@ proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} =
if hasValue: if hasValue:
await request.client.send(data) await request.client.send(data)
data.setLen 0 data.setLen 0
except HttpRequestError, ProtocolError, OSError: return Http200
except CatchableError:
if attempt < 2:
echo "[media] Retry $1/2, error: $2, url: $3" % [$(attempt + 1), getCurrentExceptionMsg(), url]
shouldRetry = true
else:
echo "[media] Proxying exception, error: $1, url: $2" % [getCurrentExceptionMsg(), url] echo "[media] Proxying exception, error: $1, url: $2" % [getCurrentExceptionMsg(), url]
result = Http404 result = Http404
finally: finally:
client.close() client.close()
if not shouldRetry:
break
template check*(code): untyped = template check*(code): untyped =
if code != Http200: if code != Http200:
@ -129,7 +153,7 @@ proc createMediaRouter*(cfg: Config) =
if getHmac(url) != request.matches[1]: if getHmac(url) != request.matches[1]:
resp Http403, showError("Failed to verify signature", cfg) resp Http403, showError("Failed to verify signature", cfg)
if ".mp4" in url or ".ts" in url or ".m4s" in url: if ".mp4" in url or ".ts" in url or ".m4s" in url or ".aac" in url:
let code = await proxyMedia(request, url) let code = await proxyMedia(request, url)
check code check code