private async Task ProcessWithDiskCache(HttpContext context, string cacheKey, ImageJobInfo info) { var cacheResult = await diskCache.GetOrCreate(cacheKey, info.EstimatedFileExtension, async (stream) => { if (info.HasParams) { logger?.LogInformation("DiskCache Miss: Processing image {VirtualPath}{QueryString}", info.FinalVirtualPath, info); var result = await info.ProcessUncached(); if (result.ResultBytes.Array == null) { throw new InvalidOperationException("Image job returned zero bytes."); } await stream.WriteAsync(result.ResultBytes.Array, result.ResultBytes.Offset, result.ResultBytes.Count, CancellationToken.None); await stream.FlushAsync(); } else { logger?.LogInformation("DiskCache Miss: Proxying image {VirtualPath}", info.FinalVirtualPath); await info.CopyPrimaryBlobToAsync(stream); } }); if (cacheResult.Result == CacheQueryResult.Miss) { GlobalPerf.Singleton.IncrementCounter("diskcache_miss"); } else if (cacheResult.Result == CacheQueryResult.Hit) { GlobalPerf.Singleton.IncrementCounter("diskcache_hit"); } else if (cacheResult.Result == CacheQueryResult.Failed) { GlobalPerf.Singleton.IncrementCounter("diskcache_timeout"); } // Note that using estimated file extension instead of parsing magic bytes will lead to incorrect content-type // values when the source file has a mismatched extension. if (cacheResult.Data != null) { if (cacheResult.Data.Length < 1) { throw new InvalidOperationException("DiskCache returned cache entry with zero bytes"); } SetCachingHeaders(context, cacheKey); await MagicBytes.ProxyToStream(cacheResult.Data, context.Response); } else { logger?.LogInformation("Serving {0}?{1} from disk cache {2}", info.FinalVirtualPath, info.CommandString, cacheResult.RelativePath); await ServeFileFromDisk(context, cacheResult.PhysicalPath, cacheKey); } }
private async Task ServeFileFromDisk(HttpContext context, string path, string etag) { await using var readStream = File.OpenRead(path); if (readStream.Length < 1) { throw new InvalidOperationException("DiskCache file entry has zero bytes"); } SetCachingHeaders(context, etag); await MagicBytes.ProxyToStream(readStream, context.Response); }
private async Task ProcessWithStreamCache(HttpContext context, string cacheKey, ImageJobInfo info) { var keyBytes = Encoding.UTF8.GetBytes(cacheKey); var typeName = streamCache.GetType().Name; var cacheResult = await streamCache.GetOrCreateBytes(keyBytes, async (cancellationToken) => { if (info.HasParams) { logger?.LogDebug("{CacheName} miss: Processing image {VirtualPath}?{Querystring}", typeName, info.FinalVirtualPath, info.ToString()); var result = await info.ProcessUncached(); if (result.ResultBytes.Array == null) { throw new InvalidOperationException("Image job returned zero bytes."); } return(new Tuple <string, ArraySegment <byte> >(result.ContentType, result.ResultBytes)); } logger?.LogDebug("{CacheName} miss: Proxying image {VirtualPath}", typeName, info.FinalVirtualPath); var bytes = await info.GetPrimaryBlobBytesAsync(); return(new Tuple <string, ArraySegment <byte> >(null, bytes)); }, CancellationToken.None, false); if (cacheResult.Status != null) { GlobalPerf.Singleton.IncrementCounter($"{typeName}_{cacheResult.Status}"); } if (cacheResult.Data != null) { await using (cacheResult.Data) { if (cacheResult.Data.Length < 1) { throw new InvalidOperationException($"{typeName} returned cache entry with zero bytes"); } SetCachingHeaders(context, cacheKey); await MagicBytes.ProxyToStream(cacheResult.Data, context.Response); } logger?.LogDebug("Serving from {CacheName} {VirtualPath}?{CommandString}", typeName, info.FinalVirtualPath, info.CommandString); } else { // TODO explore this failure path better throw new NullReferenceException("Caching failed: " + cacheResult.Status); } }
private async Task ProcessWithNoCache(HttpContext context, ImageJobInfo info) { // If we're not caching, we should always use the modified date from source blobs as part of the etag var betterCacheKey = await info.GetExactCacheKey(); if (context.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var etag) && betterCacheKey == etag) { GlobalPerf.Singleton.IncrementCounter("etag_hit"); context.Response.StatusCode = StatusCodes.Status304NotModified; context.Response.ContentLength = 0; context.Response.ContentType = null; return; } GlobalPerf.Singleton.IncrementCounter("etag_miss"); if (info.HasParams) { logger?.LogInformation("Processing image {VirtualPath} with params {CommandString}", info.FinalVirtualPath, info.CommandString); GlobalPerf.Singleton.IncrementCounter("nocache_processed"); var imageData = await info.ProcessUncached(); var imageBytes = imageData.ResultBytes; var contentType = imageData.ContentType; // write to stream context.Response.ContentType = contentType; context.Response.ContentLength = imageBytes.Count; SetCachingHeaders(context, betterCacheKey); if (imageBytes.Array == null) { throw new InvalidOperationException("Image job returned zero bytes."); } await context.Response.Body.WriteAsync(imageBytes.Array, imageBytes.Offset, imageBytes.Count); } else { logger?.LogInformation("Proxying image {VirtualPath} with params {CommandString}", info.FinalVirtualPath, info.CommandString); GlobalPerf.Singleton.IncrementCounter("nocache_proxied"); await using var sourceStream = (await info.GetPrimaryBlob()).OpenRead(); SetCachingHeaders(context, betterCacheKey); await MagicBytes.ProxyToStream(sourceStream, context.Response); } }