99using System . Linq ;
1010using System . Threading . Tasks ;
1111using Microsoft . AspNetCore . Http ;
12+ using Microsoft . AspNetCore . Http . Extensions ;
1213using Microsoft . Extensions . Logging ;
1314using Microsoft . Extensions . Options ;
1415using Microsoft . IO ;
@@ -184,12 +185,14 @@ public ImageSharpMiddleware(
184185 /// <summary>
185186 /// Performs operations upon the current request.
186187 /// </summary>
187- /// <param name="context ">The current HTTP request context.</param>
188+ /// <param name="httpContext ">The current HTTP request context.</param>
188189 /// <returns>The <see cref="Task"/>.</returns>
189- public async Task Invoke ( HttpContext context )
190+ public Task Invoke ( HttpContext httpContext ) => this . Invoke ( httpContext , false ) ;
191+
192+ private async Task Invoke ( HttpContext httpContext , bool retry )
190193 {
191194 // We expect to get concrete collection type which removes virtual dispatch concerns and enumerator allocations
192- CommandCollection commands = this . requestParser . ParseRequestCommands ( context ) ;
195+ CommandCollection commands = this . requestParser . ParseRequestCommands ( httpContext ) ;
193196
194197 if ( commands . Count > 0 )
195198 {
@@ -209,44 +212,44 @@ public async Task Invoke(HttpContext context)
209212 }
210213
211214 await this . options . OnParseCommandsAsync . Invoke (
212- new ImageCommandContext ( context , commands , this . commandParser , this . parserCulture ) ) ;
215+ new ImageCommandContext ( httpContext , commands , this . commandParser , this . parserCulture ) ) ;
213216
214217 // Get the correct service for the request
215218 IImageProvider provider = null ;
216219 foreach ( IImageProvider resolver in this . providers )
217220 {
218- if ( resolver . Match ( context ) )
221+ if ( resolver . Match ( httpContext ) )
219222 {
220223 provider = resolver ;
221224 break ;
222225 }
223226 }
224227
225228 if ( ( commands . Count == 0 && provider ? . ProcessingBehavior != ProcessingBehavior . All )
226- || provider ? . IsValidRequest ( context ) != true )
229+ || provider ? . IsValidRequest ( httpContext ) != true )
227230 {
228231 // Nothing to do. call the next delegate/middleware in the pipeline
229- await this . next ( context ) ;
232+ await this . next ( httpContext ) ;
230233 return ;
231234 }
232235
233- IImageResolver sourceImageResolver = await provider . GetAsync ( context ) ;
236+ IImageResolver sourceImageResolver = await provider . GetAsync ( httpContext ) ;
234237
235238 if ( sourceImageResolver is null )
236239 {
237240 // Log the error but let the pipeline handle the 404
238241 // by calling the next delegate/middleware in the pipeline.
239- var imageContext = new ImageContext ( context , this . options ) ;
240- this . logger . LogImageResolveFailed ( imageContext . GetDisplayUrl ( ) ) ;
241- await this . next ( context ) ;
242+ this . logger . LogImageResolveFailed ( httpContext . Request . GetDisplayUrl ( ) ) ;
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