perf(jellyfin): stream JSON proxy parsing

This commit is contained in:
2026-04-05 13:35:02 -04:00
parent ad6f521795
commit 6965bdc46d
2 changed files with 31 additions and 20 deletions
@@ -97,6 +97,21 @@ public class JellyfinProxyServiceTests
Assert.Equal(500, statusCode);
}
[Fact]
public async Task GetJsonAsync_ServerErrorWithJsonBody_ReturnsParsedErrorDocument()
{
// Arrange
SetupMockResponse(HttpStatusCode.Unauthorized, "{\"Message\":\"Token expired\"}", "application/json");
// Act
var (body, statusCode) = await _service.GetJsonAsync("Items");
// Assert
Assert.NotNull(body);
Assert.Equal(401, statusCode);
Assert.Equal("Token expired", body.RootElement.GetProperty("Message").GetString());
}
[Fact]
public async Task GetJsonAsync_WithoutClientHeaders_SendsNoAuth()
{
@@ -211,14 +211,10 @@ public class JellyfinProxyService
{
using var request = CreateClientGetRequest(url, clientHeaders, out var isBrowserStaticRequest, out var isPublicEndpoint);
var response = await _httpClient.SendAsync(request);
using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var statusCode = (int)response.StatusCode;
// Always parse the response, even for errors
// The caller needs to see 401s so the client can re-authenticate
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
if (!isBrowserStaticRequest && !isPublicEndpoint)
@@ -227,23 +223,22 @@ public class JellyfinProxyService
}
// Try to parse error response to pass through to client
if (!string.IsNullOrWhiteSpace(content))
try
{
try
{
var errorDoc = JsonDocument.Parse(content);
return (errorDoc, statusCode);
}
catch
{
// Not valid JSON, return null
}
await using var errorStream = await response.Content.ReadAsStreamAsync();
var errorDoc = await JsonDocument.ParseAsync(errorStream);
return (errorDoc, statusCode);
}
catch (JsonException)
{
// Not valid JSON, return null
}
return (null, statusCode);
}
return (JsonDocument.Parse(content), statusCode);
await using var stream = await response.Content.ReadAsStreamAsync();
return (await JsonDocument.ParseAsync(stream), statusCode);
}
private HttpRequestMessage CreateClientGetRequest(
@@ -1019,12 +1014,12 @@ public class JellyfinProxyService
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await _httpClient.SendAsync(request);
using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var statusCode = (int)response.StatusCode;
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
_logger.LogWarning("Jellyfin internal request returned {StatusCode} for {Url}: {Content}",
statusCode, url, content);
return (null, statusCode);
@@ -1032,12 +1027,13 @@ public class JellyfinProxyService
try
{
var jsonDocument = JsonDocument.Parse(content);
await using var stream = await response.Content.ReadAsStreamAsync();
var jsonDocument = await JsonDocument.ParseAsync(stream);
return (jsonDocument, statusCode);
}
catch (JsonException ex)
{
_logger.LogError(ex, "Failed to parse JSON response from {Url}: {Content}", url, content);
_logger.LogError(ex, "Failed to parse JSON response from {Url}", url);
return (null, statusCode);
}
}