Improve proxy cache behavior and diagnostics
This commit is contained in:
@@ -49,6 +49,8 @@ public sealed class MultilangController : ControllerBase
|
||||
private readonly RefreshService _refreshService;
|
||||
private readonly FanartClient _fanartClient;
|
||||
private readonly ItemsProxyCache _itemsProxyCache;
|
||||
private readonly ItemsProxyRequestCoalescer _itemsProxyRequestCoalescer;
|
||||
private readonly ItemsProxyPrecacheService _itemsProxyPrecacheService;
|
||||
private readonly ItemsProxyTransformer _itemsProxyTransformer;
|
||||
private readonly AssetStorageService _assetStorage;
|
||||
private readonly MultilangBackupService _backupService;
|
||||
@@ -67,6 +69,8 @@ public sealed class MultilangController : ControllerBase
|
||||
RefreshService refreshService,
|
||||
FanartClient fanartClient,
|
||||
ItemsProxyCache itemsProxyCache,
|
||||
ItemsProxyRequestCoalescer itemsProxyRequestCoalescer,
|
||||
ItemsProxyPrecacheService itemsProxyPrecacheService,
|
||||
ItemsProxyTransformer itemsProxyTransformer,
|
||||
AssetStorageService assetStorage,
|
||||
MultilangBackupService backupService)
|
||||
@@ -84,6 +88,8 @@ public sealed class MultilangController : ControllerBase
|
||||
_refreshService = refreshService;
|
||||
_fanartClient = fanartClient;
|
||||
_itemsProxyCache = itemsProxyCache;
|
||||
_itemsProxyRequestCoalescer = itemsProxyRequestCoalescer;
|
||||
_itemsProxyPrecacheService = itemsProxyPrecacheService;
|
||||
_itemsProxyTransformer = itemsProxyTransformer;
|
||||
_assetStorage = assetStorage;
|
||||
_backupService = backupService;
|
||||
@@ -264,7 +270,7 @@ public sealed class MultilangController : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("ItemsProxy")]
|
||||
public async Task<IActionResult> ItemsProxy([FromQuery] string url, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> ItemsProxy([FromQuery] string url, [FromQuery] bool precache, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
return BadRequest(new { Error = "MissingUrl" });
|
||||
@@ -286,6 +292,18 @@ public sealed class MultilangController : ControllerBase
|
||||
var cfg = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
||||
var cacheAllowed = rules.Enabled && cfg.ItemsProxyCacheTtlMinutes > 0 && cfg.ItemsProxyCacheMaxMiB > 0;
|
||||
var cacheTtl = TimeSpan.FromMinutes(Math.Max(1, cfg.ItemsProxyCacheTtlMinutes));
|
||||
if (!precache && cacheAllowed)
|
||||
{
|
||||
var baseUri = new Uri($"{Request.Scheme}://{Request.Host}{Request.PathBase}/");
|
||||
_itemsProxyPrecacheService.ObserveUserActivity(
|
||||
baseUri,
|
||||
userId,
|
||||
token,
|
||||
proxyRequest.Controls.ClientLocale,
|
||||
cfg.PrecacheMovieLibraries,
|
||||
cfg.PrecacheTvShowLibraries);
|
||||
}
|
||||
|
||||
if (cacheAllowed && _itemsProxyCache.TryGet(proxyRequest.CacheKey, cacheTtl, out var cached))
|
||||
{
|
||||
totalSw.Stop();
|
||||
@@ -320,16 +338,27 @@ public sealed class MultilangController : ControllerBase
|
||||
}
|
||||
|
||||
var upstreamSw = Stopwatch.StartNew();
|
||||
var http = _httpClientFactory.CreateClient();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, proxyRequest.Upstream);
|
||||
request.Headers.TryAddWithoutValidation("X-Emby-Token", token);
|
||||
request.Headers.TryAddWithoutValidation("X-MediaBrowser-Token", token);
|
||||
using var response = await http.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
var upstream = await _itemsProxyRequestCoalescer.GetOrFetchAsync(
|
||||
proxyRequest.NormalizedUrlForCache,
|
||||
async sharedCancellationToken =>
|
||||
{
|
||||
var http = _httpClientFactory.CreateClient();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, proxyRequest.Upstream);
|
||||
request.Headers.TryAddWithoutValidation("X-Emby-Token", token);
|
||||
request.Headers.TryAddWithoutValidation("X-MediaBrowser-Token", token);
|
||||
using var response = await http.SendAsync(request, sharedCancellationToken).ConfigureAwait(false);
|
||||
var body = await response.Content.ReadAsStringAsync(sharedCancellationToken).ConfigureAwait(false);
|
||||
return new ItemsProxyUpstreamResponse(
|
||||
(int)response.StatusCode,
|
||||
body,
|
||||
response.Content.Headers.ContentType?.ToString() ?? "application/json");
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
upstreamSw.Stop();
|
||||
var body = upstream.Response.Body;
|
||||
string[] responseItemIds = [];
|
||||
var transformMs = 0L;
|
||||
if (response.IsSuccessStatusCode && rules.Enabled)
|
||||
if (upstream.Response.IsSuccessStatusCode && rules.Enabled)
|
||||
{
|
||||
var transformSw = Stopwatch.StartNew();
|
||||
var transformed = _itemsProxyTransformer.TransformItemsResponse(body, rules, proxyRequest.Controls);
|
||||
@@ -340,11 +369,11 @@ public sealed class MultilangController : ControllerBase
|
||||
}
|
||||
totalSw.Stop();
|
||||
|
||||
var contentType = response.Content.Headers.ContentType?.ToString() ?? "application/json";
|
||||
var contentType = upstream.Response.ContentType;
|
||||
var bodySizeBytes = System.Text.Encoding.UTF8.GetByteCount(body);
|
||||
var cacheStored = false;
|
||||
if (cacheAllowed &&
|
||||
response.IsSuccessStatusCode &&
|
||||
upstream.Response.IsSuccessStatusCode &&
|
||||
responseItemIds.Length > 0 &&
|
||||
totalSw.ElapsedMilliseconds >= Math.Max(0, cfg.ItemsProxyCacheThresholdMs))
|
||||
{
|
||||
@@ -362,7 +391,7 @@ public sealed class MultilangController : ControllerBase
|
||||
|
||||
if (cacheAllowed &&
|
||||
!cacheStored &&
|
||||
response.IsSuccessStatusCode &&
|
||||
upstream.Response.IsSuccessStatusCode &&
|
||||
responseItemIds.Length > 0)
|
||||
{
|
||||
_itemsProxyCache.StoreMicro(
|
||||
@@ -380,16 +409,17 @@ public sealed class MultilangController : ControllerBase
|
||||
proxyRequest.NormalizedUrlForCache,
|
||||
cacheHit: false,
|
||||
cacheStored: cacheStored,
|
||||
statusCode: (int)response.StatusCode,
|
||||
statusCode: upstream.Response.StatusCode,
|
||||
itemCount: responseItemIds.Length,
|
||||
totalMs: totalSw.ElapsedMilliseconds,
|
||||
upstreamMs: upstreamSw.ElapsedMilliseconds,
|
||||
transformMs: transformMs,
|
||||
sizeBytes: bodySizeBytes);
|
||||
sizeBytes: bodySizeBytes,
|
||||
inFlightCoalesced: upstream.JoinedExistingRequest);
|
||||
|
||||
return new ContentResult
|
||||
{
|
||||
StatusCode = (int)response.StatusCode,
|
||||
StatusCode = upstream.Response.StatusCode,
|
||||
Content = body,
|
||||
ContentType = contentType
|
||||
};
|
||||
@@ -1443,6 +1473,10 @@ public sealed class AdminConfigDto
|
||||
|
||||
public int ItemsProxyCacheMaxMiB { get; set; }
|
||||
|
||||
public bool PrecacheMovieLibraries { get; set; } = true;
|
||||
|
||||
public bool PrecacheTvShowLibraries { get; set; } = true;
|
||||
|
||||
public string AssetStorageMode { get; set; } = "url";
|
||||
|
||||
public bool IgnoreArticlesWhenSorting { get; set; } = true;
|
||||
@@ -1469,6 +1503,8 @@ public sealed class AdminConfigDto
|
||||
ItemsProxyCacheThresholdMs = cfg.ItemsProxyCacheThresholdMs,
|
||||
ItemsProxyCacheTtlMinutes = cfg.ItemsProxyCacheTtlMinutes,
|
||||
ItemsProxyCacheMaxMiB = cfg.ItemsProxyCacheMaxMiB,
|
||||
PrecacheMovieLibraries = cfg.PrecacheMovieLibraries,
|
||||
PrecacheTvShowLibraries = cfg.PrecacheTvShowLibraries,
|
||||
AssetStorageMode = NormalizeAssetStorageMode(cfg.AssetStorageMode),
|
||||
IgnoreArticlesWhenSorting = cfg.IgnoreArticlesWhenSorting,
|
||||
JellyfinTitleLanguageFallback = cfg.JellyfinTitleLanguageFallback,
|
||||
@@ -1493,6 +1529,8 @@ public sealed class AdminConfigDto
|
||||
ItemsProxyCacheThresholdMs = Math.Max(0, ItemsProxyCacheThresholdMs),
|
||||
ItemsProxyCacheTtlMinutes = Math.Max(1, ItemsProxyCacheTtlMinutes),
|
||||
ItemsProxyCacheMaxMiB = Math.Max(1, ItemsProxyCacheMaxMiB),
|
||||
PrecacheMovieLibraries = PrecacheMovieLibraries,
|
||||
PrecacheTvShowLibraries = PrecacheTvShowLibraries,
|
||||
AssetStorageMode = NormalizeAssetStorageMode(AssetStorageMode),
|
||||
IgnoreArticlesWhenSorting = IgnoreArticlesWhenSorting,
|
||||
JellyfinTitleLanguageFallback = JellyfinTitleLanguageFallback?.Trim() ?? string.Empty,
|
||||
|
||||
Reference in New Issue
Block a user