Ver Fonte

Add retry logic and timeout to media proxy

Zed há 2 meses atrás
pai
commit
581c6f1714
1 ficheiros alterados com 65 adições e 41 exclusões
  1. 65 41
      src/routes/media.nim

+ 65 - 41
src/routes/media.nim

@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 import uri, strutils, httpclient, os, hashes, base64, re
 import asynchttpserver, asyncstreams, asyncfile, asyncnet
+import asyncdispatch
 
 import jester
 
@@ -32,47 +33,70 @@ template respond*(req: asynchttpserver.Request; headers) =
 
 proc proxyMedia*(req: jester.Request; url: string): Future[HttpCode] {.async.} =
   result = Http200
-  let
-    request = req.getNativeReq()
-    client = newAsyncHttpClient(maxRedirects = 0)
-
-  try:
-    let res = await client.get(url)
-    if res.status != "200 OK":
-      if res.status != "404 Not Found":
-        echo "[media] Proxying failed, status: $1, url: $2" % [res.status, url]
-      return Http404
-
-    let hashed = $hash(url)
-    if request.headers.getOrDefault("If-None-Match") == hashed:
-      return Http304
-
-    let contentLength =
-      if res.headers.hasKey("content-length"):
-        res.headers["content-length", 0]
+  let request = req.getNativeReq()
+
+  for attempt in 0 .. 2:
+    let client = newAsyncHttpClient(maxRedirects = 0)
+    var shouldRetry = false
+    try:
+      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 == "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]
+            return Http404
+        else:
+          let hashed = $hash(url)
+          if request.headers.getOrDefault("If-None-Match") == hashed:
+            return Http304
+
+          let contentLength =
+            if res.headers.hasKey("content-length"):
+              res.headers["content-length", 0]
+            else:
+              ""
+
+          let headers = newHttpHeaders({
+            "content-type": res.headers["content-type", 0],
+            "content-length": contentLength,
+            "cache-control": maxAge,
+            "etag": hashed
+          })
+
+          respond(request, headers)
+
+          var (hasValue, data) = (true, "")
+          while hasValue:
+            (hasValue, data) = await res.bodyStream.read()
+            if hasValue:
+              await request.client.send(data)
+          data.setLen 0
+          return Http200
+    except CatchableError:
+      if attempt < 2:
+        echo "[media] Retry $1/2, error: $2, url: $3" % [$(attempt + 1), getCurrentExceptionMsg(), url]
+        shouldRetry = true
       else:
-        ""
-
-    let headers = newHttpHeaders({
-      "content-type": res.headers["content-type", 0],
-      "content-length": contentLength,
-      "cache-control": maxAge,
-      "etag": hashed
-    })
-
-    respond(request, headers)
-
-    var (hasValue, data) = (true, "")
-    while hasValue:
-      (hasValue, data) = await res.bodyStream.read()
-      if hasValue:
-        await request.client.send(data)
-    data.setLen 0
-  except HttpRequestError, ProtocolError, OSError:
-    echo "[media] Proxying exception, error: $1, url: $2" % [getCurrentExceptionMsg(), url]
-    result = Http404
-  finally:
-    client.close()
+        echo "[media] Proxying exception, error: $1, url: $2" % [getCurrentExceptionMsg(), url]
+        result = Http404
+    finally:
+      client.close()
+    if not shouldRetry:
+      break
 
 template check*(code): untyped =
   if code != Http200:
@@ -129,7 +153,7 @@ proc createMediaRouter*(cfg: Config) =
       if getHmac(url) != request.matches[1]:
         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)
         check code