Skip to content

Commit 4a37d3f

Browse files
committed
Send response directly from output stream rather than re-reading from the cache
1 parent d0e2408 commit 4a37d3f

File tree

1 file changed

+88
-91
lines changed

1 file changed

+88
-91
lines changed

src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs

Lines changed: 88 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ private async Task ProcessRequestAsync(
277277

278278
if (!readResult.IsNewOrUpdated)
279279
{
280-
await this.SendResponseAsync(imageContext, key, readResult.CacheImageMetadata, readResult.Resolver);
280+
await this.SendResponseAsync(imageContext, key, readResult.CacheImageMetadata, readResult.Resolver, null);
281281
return;
282282
}
283283

@@ -288,104 +288,92 @@ private async Task ProcessRequestAsync(
288288
// This reduces the overheads of unnecessary processing.
289289
ImageWorkerResult writeResult;
290290

291-
using (await this.asyncKeyLock.WriterLockAsync(key))
291+
RecyclableMemoryStream outStream = null;
292+
try
292293
{
293-
RecyclableMemoryStream outStream = null;
294-
try
294+
using (await this.asyncKeyLock.WriterLockAsync(key))
295295
{
296-
ImageCacheMetadata cachedImageMetadata = default;
297-
outStream = new RecyclableMemoryStream(this.options.MemoryStreamManager);
298-
IImageFormat format;
299-
300-
// 14.9.3 CacheControl Max-Age
301-
// Check to see if the source metadata has a CacheControl Max-Age value
302-
// and use it to override the default max age from our options.
303-
TimeSpan maxAge = this.options.BrowserMaxAge;
304-
if (!sourceImageMetadata.CacheControlMaxAge.Equals(TimeSpan.MinValue))
305-
{
306-
maxAge = sourceImageMetadata.CacheControlMaxAge;
307-
}
308-
309-
using (Stream inStream = await sourceImageResolver.OpenReadAsync())
296+
try
310297
{
311-
// No commands? We simply copy the stream across.
312-
if (commands.Count == 0)
298+
ImageCacheMetadata cachedImageMetadata = default;
299+
outStream = new RecyclableMemoryStream(this.options.MemoryStreamManager);
300+
IImageFormat format;
301+
302+
// 14.9.3 CacheControl Max-Age
303+
// Check to see if the source metadata has a CacheControl Max-Age value
304+
// and use it to override the default max age from our options.
305+
TimeSpan maxAge = this.options.BrowserMaxAge;
306+
if (!sourceImageMetadata.CacheControlMaxAge.Equals(TimeSpan.MinValue))
313307
{
314-
await inStream.CopyToAsync(outStream);
315-
outStream.Position = 0;
316-
format = await Image.DetectFormatAsync(this.options.Configuration, outStream);
308+
maxAge = sourceImageMetadata.CacheControlMaxAge;
317309
}
318-
else
310+
311+
using (Stream inStream = await sourceImageResolver.OpenReadAsync())
319312
{
320-
using var image = FormattedImage.Load(this.options.Configuration, inStream);
313+
// No commands? We simply copy the stream across.
314+
if (commands.Count == 0)
315+
{
316+
await inStream.CopyToAsync(outStream);
317+
outStream.Position = 0;
318+
format = await Image.DetectFormatAsync(this.options.Configuration, outStream);
319+
}
320+
else
321+
{
322+
using var image = FormattedImage.Load(this.options.Configuration, inStream);
321323

322-
image.Process(
323-
this.logger,
324-
this.processors,
325-
commands,
326-
this.commandParser,
327-
this.parserCulture);
324+
image.Process(
325+
this.logger,
326+
this.processors,
327+
commands,
328+
this.commandParser,
329+
this.parserCulture);
328330

329-
await this.options.OnBeforeSaveAsync.Invoke(image);
331+
await this.options.OnBeforeSaveAsync.Invoke(image);
330332

331-
image.Save(outStream);
332-
format = image.Format;
333+
image.Save(outStream);
334+
format = image.Format;
335+
}
333336
}
334-
}
335-
336-
// Allow for any further optimization of the image.
337-
outStream.Position = 0;
338-
string contentType = format.DefaultMimeType;
339-
string extension = this.formatUtilities.GetExtensionFromContentType(contentType);
340-
await this.options.OnProcessedAsync.Invoke(new ImageProcessingContext(context, outStream, commands, contentType, extension));
341-
outStream.Position = 0;
342-
343-
cachedImageMetadata = new ImageCacheMetadata(
344-
sourceImageMetadata.LastWriteTimeUtc,
345-
DateTime.UtcNow,
346-
contentType,
347-
maxAge,
348-
outStream.Length);
349-
350-
// Save the image to the cache and send the response to the caller.
351-
await this.cache.SetAsync(key, outStream, cachedImageMetadata);
352-
353-
// Remove any resolver from the cache so we always resolve next request
354-
// for the same key.
355-
CacheResolverLru.TryRemove(key);
356-
357-
// Place the resolver in the lru cache.
358-
(IImageCacheResolver ImageCacheResolver, ImageCacheMetadata ImageCacheMetadata) cachedImage = await
359-
CacheResolverLru.GetOrAddAsync(
360-
key,
361-
async k =>
362-
{
363-
IImageCacheResolver resolver = await this.cache.GetAsync(k);
364-
ImageCacheMetadata metadata = default;
365-
if (resolver != null)
366-
{
367-
metadata = await resolver.GetMetaDataAsync();
368-
}
369-
370-
return (resolver, metadata);
371-
});
372337

373-
writeResult = new ImageWorkerResult(cachedImage.ImageCacheMetadata, cachedImage.ImageCacheResolver);
374-
}
375-
catch (Exception ex)
376-
{
377-
// Log the error internally then rethrow.
378-
// We don't call next here, the pipeline will automatically handle it
379-
this.logger.LogImageProcessingFailed(imageContext.GetDisplayUrl(), ex);
380-
throw;
381-
}
382-
finally
383-
{
384-
await this.StreamDisposeAsync(outStream);
338+
// Allow for any further optimization of the image.
339+
outStream.Position = 0;
340+
string contentType = format.DefaultMimeType;
341+
string extension = this.formatUtilities.GetExtensionFromContentType(contentType);
342+
await this.options.OnProcessedAsync.Invoke(new ImageProcessingContext(context, outStream, commands, contentType, extension));
343+
outStream.Position = 0;
344+
345+
cachedImageMetadata = new ImageCacheMetadata(
346+
sourceImageMetadata.LastWriteTimeUtc,
347+
DateTime.UtcNow,
348+
contentType,
349+
maxAge,
350+
outStream.Length);
351+
352+
// Save the image to the cache and send the response to the caller.
353+
await this.cache.SetAsync(key, outStream, cachedImageMetadata);
354+
outStream.Position = 0;
355+
356+
// Remove any resolver from the cache so we always resolve next request
357+
// for the same key.
358+
CacheResolverLru.TryRemove(key);
359+
360+
writeResult = new ImageWorkerResult(cachedImageMetadata, null);
361+
}
362+
catch (Exception ex)
363+
{
364+
// Log the error internally then rethrow.
365+
// We don't call next here, the pipeline will automatically handle it
366+
this.logger.LogImageProcessingFailed(imageContext.GetDisplayUrl(), ex);
367+
throw;
368+
}
385369
}
386-
}
387370

388-
await this.SendResponseAsync(imageContext, key, writeResult.CacheImageMetadata, writeResult.Resolver);
371+
await this.SendResponseAsync(imageContext, key, writeResult.CacheImageMetadata, writeResult.Resolver, outStream);
372+
}
373+
finally
374+
{
375+
await this.StreamDisposeAsync(outStream);
376+
}
389377
}
390378

391379
private ValueTask StreamDisposeAsync(Stream stream)
@@ -411,8 +399,8 @@ private ValueTask StreamDisposeAsync(Stream stream)
411399
}
412400

413401
private async Task<ImageWorkerResult> IsNewOrUpdatedAsync(
414-
IImageResolver sourceImageResolver,
415-
string key)
402+
IImageResolver sourceImageResolver,
403+
string key)
416404
{
417405
// Get the source metadata for processing, storing the result for future checks.
418406
ImageMetadata sourceImageMetadata = await
@@ -464,7 +452,8 @@ private async Task SendResponseAsync(
464452
ImageContext imageContext,
465453
string key,
466454
ImageCacheMetadata metadata,
467-
IImageCacheResolver cacheResolver)
455+
IImageCacheResolver cacheResolver,
456+
Stream stream)
468457
{
469458
imageContext.ComprehendRequestHeaders(metadata.CacheLastWriteTimeUtc, metadata.ContentLength);
470459

@@ -480,11 +469,19 @@ private async Task SendResponseAsync(
480469

481470
this.logger.LogImageServed(imageContext.GetDisplayUrl(), key);
482471

483-
// When stream is null we're sending from the cache.
484-
using (Stream stream = await cacheResolver.OpenReadAsync())
472+
// If stream is not null, then send it directly. Otherwise, use the cacheResolver
473+
// to load from the cache.
474+
if (stream != null)
485475
{
486476
await imageContext.SendAsync(stream, metadata);
487477
}
478+
else
479+
{
480+
using (Stream cacheStream = await cacheResolver.OpenReadAsync())
481+
{
482+
await imageContext.SendAsync(cacheStream, metadata);
483+
}
484+
}
488485

489486
return;
490487

0 commit comments

Comments
 (0)