Skip to content

Commit 5fca9ce

Browse files
Allow runtime physical deletion of cached images
1 parent 21a6bc1 commit 5fca9ce

1 file changed

Lines changed: 46 additions & 21 deletions

File tree

src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,14 @@ public ImageSharpMiddleware(
184184
/// <summary>
185185
/// Performs operations upon the current request.
186186
/// </summary>
187-
/// <param name="context">The current HTTP request context.</param>
187+
/// <param name="httpContext">The current HTTP request context.</param>
188188
/// <returns>The <see cref="Task"/>.</returns>
189-
public async Task Invoke(HttpContext context)
189+
public Task Invoke(HttpContext httpContext) => this.Invoke(httpContext, false);
190+
191+
private async Task Invoke(HttpContext httpContext, bool retry)
190192
{
191193
// We expect to get concrete collection type which removes virtual dispatch concerns and enumerator allocations
192-
CommandCollection commands = this.requestParser.ParseRequestCommands(context);
194+
CommandCollection commands = this.requestParser.ParseRequestCommands(httpContext);
193195

194196
if (commands.Count > 0)
195197
{
@@ -209,44 +211,45 @@ public async Task Invoke(HttpContext context)
209211
}
210212

211213
await this.options.OnParseCommandsAsync.Invoke(
212-
new ImageCommandContext(context, commands, this.commandParser, this.parserCulture));
214+
new ImageCommandContext(httpContext, commands, this.commandParser, this.parserCulture));
213215

214216
// Get the correct service for the request
215217
IImageProvider provider = null;
216218
foreach (IImageProvider resolver in this.providers)
217219
{
218-
if (resolver.Match(context))
220+
if (resolver.Match(httpContext))
219221
{
220222
provider = resolver;
221223
break;
222224
}
223225
}
224226

225227
if ((commands.Count == 0 && provider?.ProcessingBehavior != ProcessingBehavior.All)
226-
|| provider?.IsValidRequest(context) != true)
228+
|| provider?.IsValidRequest(httpContext) != true)
227229
{
228230
// Nothing to do. call the next delegate/middleware in the pipeline
229-
await this.next(context);
231+
await this.next(httpContext);
230232
return;
231233
}
232234

233-
IImageResolver sourceImageResolver = await provider.GetAsync(context);
235+
IImageResolver sourceImageResolver = await provider.GetAsync(httpContext);
234236

235237
if (sourceImageResolver is null)
236238
{
237239
// Log the error but let the pipeline handle the 404
238240
// by calling the next delegate/middleware in the pipeline.
239-
var imageContext = new ImageContext(context, this.options);
241+
var imageContext = new ImageContext(httpContext, this.options);
240242
this.logger.LogImageResolveFailed(imageContext.GetDisplayUrl());
241-
await this.next(context);
243+
await this.next(httpContext);
242244
return;
243245
}
244246

245247
await this.ProcessRequestAsync(
246-
context,
248+
httpContext,
247249
sourceImageResolver,
248-
new ImageContext(context, this.options),
249-
commands);
250+
new ImageContext(httpContext, this.options),
251+
commands,
252+
retry);
250253
}
251254

252255
private void StripUnknownCommands(CommandCollection commands, int startAtIndex)
@@ -263,14 +266,15 @@ private void StripUnknownCommands(CommandCollection commands, int startAtIndex)
263266
}
264267

265268
private async Task ProcessRequestAsync(
266-
HttpContext context,
269+
HttpContext httpContext,
267270
IImageResolver sourceImageResolver,
268271
ImageContext imageContext,
269-
CommandCollection commands)
272+
CommandCollection commands,
273+
bool retry)
270274
{
271275
// Create a hashed cache key
272276
string key = this.cacheHash.Create(
273-
this.cacheKey.Create(context, commands),
277+
this.cacheKey.Create(httpContext, commands),
274278
this.options.CacheHashLength);
275279

276280
// Check the cache, if present, not out of date and not requiring an update
@@ -283,7 +287,7 @@ private async Task ProcessRequestAsync(
283287

284288
if (!readResult.IsNewOrUpdated)
285289
{
286-
await this.SendResponseAsync(imageContext, key, readResult.CacheImageMetadata, readResult.Resolver, null);
290+
await this.SendResponseAsync(httpContext, imageContext, key, readResult.CacheImageMetadata, readResult.Resolver, null, retry);
287291
return;
288292
}
289293

@@ -379,7 +383,7 @@ private async Task ProcessRequestAsync(
379383
outStream.Position = 0;
380384
string contentType = format.DefaultMimeType;
381385
string extension = this.formatUtilities.GetExtensionFromContentType(contentType);
382-
await this.options.OnProcessedAsync.Invoke(new ImageProcessingContext(context, outStream, commands, contentType, extension));
386+
await this.options.OnProcessedAsync.Invoke(new ImageProcessingContext(httpContext, outStream, commands, contentType, extension));
383387
outStream.Position = 0;
384388

385389
cachedImageMetadata = new ImageCacheMetadata(
@@ -409,7 +413,7 @@ private async Task ProcessRequestAsync(
409413
}
410414
}
411415

412-
await this.SendResponseAsync(imageContext, key, readResult.CacheImageMetadata, readResult.Resolver, outStream);
416+
await this.SendResponseAsync(httpContext, imageContext, key, readResult.CacheImageMetadata, readResult.Resolver, outStream, retry);
413417
}
414418
finally
415419
{
@@ -490,11 +494,13 @@ private async Task<ImageWorkerResult> IsNewOrUpdatedAsync(
490494
}
491495

492496
private async Task SendResponseAsync(
497+
HttpContext httpContext,
493498
ImageContext imageContext,
494499
string key,
495500
ImageCacheMetadata metadata,
496501
IImageCacheResolver cacheResolver,
497-
Stream stream)
502+
Stream stream,
503+
bool retry)
498504
{
499505
imageContext.ComprehendRequestHeaders(metadata.CacheLastWriteTimeUtc, metadata.ContentLength);
500506

@@ -518,10 +524,29 @@ private async Task SendResponseAsync(
518524
}
519525
else
520526
{
521-
using (Stream cacheStream = await cacheResolver.OpenReadAsync())
527+
try
522528
{
529+
using Stream cacheStream = await cacheResolver.OpenReadAsync();
523530
await imageContext.SendAsync(cacheStream, metadata);
524531
}
532+
catch (Exception ex)
533+
{
534+
if (!retry)
535+
{
536+
// The image has failed to be returned from the cache.
537+
// This can happen if the cached image has been physically deleted but the item is still in the LRU cache.
538+
// We'll retry running the request again in it's entirety. This ensures any changes to the source are tracked also.
539+
CacheResolverLru.TryRemove(key);
540+
await this.Invoke(httpContext);
541+
return;
542+
}
543+
544+
// We've already tried to run this request before.
545+
// Log the error internally then rethrow.
546+
// We don't call next here, the pipeline will automatically handle it
547+
this.logger.LogImageProcessingFailed(imageContext.GetDisplayUrl(), ex);
548+
throw;
549+
}
525550
}
526551

527552
return;

0 commit comments

Comments
 (0)