Browse Source

Enhanced HTTP Range request support for. strm file

Forward the Range, Accept-Ranges, and Content-
Range headers, improve User-Agent handling,
and adjust the default Content-Type.
sususu98 3 months ago
parent
commit
a7891b3f2d
1 changed files with 55 additions and 5 deletions
  1. 55 5
      Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs

+ 55 - 5
Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs

@@ -32,17 +32,67 @@ public static class FileStreamResponseHelpers
         HttpContext httpContext,
         HttpContext httpContext,
         CancellationToken cancellationToken = default)
         CancellationToken cancellationToken = default)
     {
     {
+        using var requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(state.MediaPath));
+
+        // Forward User-Agent if provided
         if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent))
         if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent))
         {
         {
-            httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, useragent);
+            // Clear default and add specific one if exists, otherwise HttpClient default might be used
+            requestMessage.Headers.UserAgent.Clear();
+            requestMessage.Headers.TryAddWithoutValidation(HeaderNames.UserAgent, useragent);
+        }
+
+        // Forward Range header if present in the client request
+        if (httpContext.Request.Headers.TryGetValue(HeaderNames.Range, out var rangeValue))
+        {
+            var rangeString = rangeValue.ToString();
+            if (!string.IsNullOrEmpty(rangeString))
+            {
+                requestMessage.Headers.Range = System.Net.Http.Headers.RangeHeaderValue.Parse(rangeString);
+            }
         }
         }
 
 
-        // Can't dispose the response as it's required up the call chain.
-        var response = await httpClient.GetAsync(new Uri(state.MediaPath), HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
-        var contentType = response.Content.Headers.ContentType?.ToString() ?? MediaTypeNames.Text.Plain;
+        // Send the request to the upstream server
+        // Use ResponseHeadersRead to avoid downloading the whole content immediately
+        var response = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
 
 
-        httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none";
+        // Check if the upstream server supports range requests and acted upon our Range header
+        bool upstreamSupportsRange = response.StatusCode == System.Net.HttpStatusCode.PartialContent;
+        string acceptRangesValue = "none";
+        if (response.Headers.TryGetValues(HeaderNames.AcceptRanges, out var acceptRangesHeaders))
+        {
+            // Prefer upstream server's Accept-Ranges header if available
+             acceptRangesValue = string.Join(", ", acceptRangesHeaders);
+             upstreamSupportsRange |= acceptRangesValue.Contains("bytes", StringComparison.OrdinalIgnoreCase);
+        }
+        else if (upstreamSupportsRange) // If we got 206 but no Accept-Ranges header, assume bytes
+        {
+             acceptRangesValue = "bytes";
+        }
+
+        // Set Accept-Ranges header for the client based on upstream support
+        httpContext.Response.Headers[HeaderNames.AcceptRanges] = acceptRangesValue;
+
+        // Set Content-Range header if upstream provided it (implies partial content)
+        if (response.Content.Headers.ContentRange is not null)
+        {
+             httpContext.Response.Headers[HeaderNames.ContentRange] = response.Content.Headers.ContentRange.ToString();
+        }
+
+        // Set Content-Length header. For partial content, this is the length of the partial segment.
+        if (response.Content.Headers.ContentLength.HasValue)
+        {
+             httpContext.Response.ContentLength = response.Content.Headers.ContentLength.Value;
+        }
+
+        // Set Content-Type header
+        var contentType = response.Content.Headers.ContentType?.ToString() ?? MediaTypeNames.Application.Octet; // Use a more generic default
+
+        // Set the status code for the client response (e.g., 200 OK or 206 Partial Content)
+        httpContext.Response.StatusCode = (int)response.StatusCode;
 
 
+        // Return the stream from the upstream server
+        // IMPORTANT: Do not dispose the response stream here, FileStreamResult will handle it.
         return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
         return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
     }
     }