Skip to content

Commit 33a6a8d

Browse files
Abstract cache key creation to ICacheKey
1 parent 9c0231e commit 33a6a8d

6 files changed

Lines changed: 112 additions & 32 deletions

File tree

samples/ImageSharp.Web.Sample/Startup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public void ConfigureServices(IServiceCollection services)
5454
provider.GetRequiredService<IOptions<ImageSharpMiddlewareOptions>>(),
5555
provider.GetRequiredService<FormatUtilities>());
5656
})
57+
.SetCacheKey<CacheKey>()
5758
.SetCacheHash<CacheHash>()
5859
.AddProvider<PhysicalFileSystemProvider>()
5960
.AddProcessor<ResizeWebProcessor>()
@@ -129,6 +130,7 @@ private void ConfigureCustomServicesAndCustomOptions(IServiceCollection services
129130
provider.GetRequiredService<IOptions<ImageSharpMiddlewareOptions>>(),
130131
provider.GetRequiredService<FormatUtilities>());
131132
})
133+
.SetCacheKey<CacheKey>()
132134
.SetCacheHash<CacheHash>()
133135
.ClearProviders()
134136
.AddProvider<PhysicalFileSystemProvider>()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Text;
5+
using Microsoft.AspNetCore.Http;
6+
using SixLabors.ImageSharp.Web.Commands;
7+
8+
namespace SixLabors.ImageSharp.Web.Caching
9+
{
10+
/// <summary>
11+
/// Creates a cache key based on the lowercased request host, path and commands.
12+
/// </summary>
13+
public class CacheKey : ICacheKey
14+
{
15+
/// <inheritdoc/>
16+
public string Create(HttpContext context, CommandCollection commands)
17+
{
18+
var sb = new StringBuilder(context.Request.Host.ToString());
19+
20+
string pathBase = context.Request.PathBase.ToString();
21+
if (!string.IsNullOrWhiteSpace(pathBase))
22+
{
23+
sb.AppendFormat("{0}/", pathBase);
24+
}
25+
26+
string path = context.Request.Path.ToString();
27+
if (!string.IsNullOrWhiteSpace(path))
28+
{
29+
sb.Append(path);
30+
}
31+
32+
sb.Append(QueryString.Create(commands));
33+
34+
return sb.ToString().ToLowerInvariant();
35+
}
36+
}
37+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using Microsoft.AspNetCore.Http;
5+
using SixLabors.ImageSharp.Web.Commands;
6+
7+
namespace SixLabors.ImageSharp.Web.Caching
8+
{
9+
/// <summary>
10+
/// Defines a contract that allows the creation of cache keys (used by <see cref="ICacheHash"/> to create hashed file names for storing cached images).
11+
/// </summary>
12+
public interface ICacheKey
13+
{
14+
/// <summary>
15+
/// Creates the cache key based on the specified context and commands.
16+
/// </summary>
17+
/// <param name="context">The HTTP context.</param>
18+
/// <param name="commands">The commands.</param>
19+
/// <returns>
20+
/// The cache key.
21+
/// </returns>
22+
string Create(HttpContext context, CommandCollection commands);
23+
}
24+
}

src/ImageSharp.Web/DependencyInjection/ImageSharpBuilderExtensions.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,33 @@ public static IImageSharpBuilder SetCache(this IImageSharpBuilder builder, Func<
9696
return builder;
9797
}
9898

99+
/// <summary>
100+
/// Sets the given <see cref="ICacheKey"/> adding it to the service collection.
101+
/// </summary>
102+
/// <typeparam name="T">The type of class implementing <see cref="ICacheKey"/> to add.</typeparam>
103+
/// <param name="builder">The core builder.</param>
104+
/// <returns>The <see cref="IImageSharpBuilder"/>.</returns>
105+
public static IImageSharpBuilder SetCacheKey<T>(this IImageSharpBuilder builder)
106+
where T : class, ICacheKey
107+
{
108+
var descriptor = new ServiceDescriptor(typeof(ICacheKey), typeof(T), ServiceLifetime.Singleton);
109+
builder.Services.Replace(descriptor);
110+
return builder;
111+
}
112+
113+
/// <summary>
114+
/// Sets the given <see cref="ICacheKey"/> adding it to the service collection.
115+
/// </summary>
116+
/// <param name="builder">The core builder.</param>
117+
/// <param name="implementationFactory">The factory method for returning a <see cref="ICacheKey"/>.</param>
118+
/// <returns>The <see cref="IImageSharpBuilder"/>.</returns>
119+
public static IImageSharpBuilder SetCacheKey(this IImageSharpBuilder builder, Func<IServiceProvider, ICacheKey> implementationFactory)
120+
{
121+
var descriptor = new ServiceDescriptor(typeof(ICacheKey), implementationFactory, ServiceLifetime.Singleton);
122+
builder.Services.Replace(descriptor);
123+
return builder;
124+
}
125+
99126
/// <summary>
100127
/// Sets the given <see cref="ICacheHash"/> adding it to the service collection.
101128
/// </summary>

src/ImageSharp.Web/DependencyInjection/ServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ private static void AddDefaultServices(
6464

6565
builder.SetCache<PhysicalFileSystemCache>();
6666

67+
builder.SetCacheKey<CacheKey>();
68+
6769
builder.SetCacheHash<CacheHash>();
6870

6971
builder.AddProvider<PhysicalFileSystemProvider>();

src/ImageSharp.Web/Middleware/ImageSharpMiddleware.cs

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ private static readonly ConcurrentTLruCache<string, ImageMetadata> SourceMetadat
7070
/// </summary>
7171
private readonly IImageWebProcessor[] processors;
7272

73+
/// <summary>
74+
/// The cache key.
75+
/// </summary>
76+
private readonly ICacheKey cacheKey;
77+
7378
/// <summary>
7479
/// The image cache.
7580
/// </summary>
@@ -115,6 +120,7 @@ private static readonly ConcurrentTLruCache<string, ImageMetadata> SourceMetadat
115120
/// <param name="resolvers">A collection of <see cref="IImageProvider"/> instances used to resolve images.</param>
116121
/// <param name="processors">A collection of <see cref="IImageWebProcessor"/> instances used to process images.</param>
117122
/// <param name="cache">An <see cref="IImageCache"/> instance used for caching images.</param>
123+
/// <param name="cacheKey">An <see cref="ICacheKey"/> instance used for creating cache keys.</param>
118124
/// <param name="cacheHash">An <see cref="ICacheHash"/>instance used for calculating cached file names.</param>
119125
/// <param name="commandParser">The command parser</param>
120126
/// <param name="formatUtilities">Contains various format helper methods based on the current configuration.</param>
@@ -127,6 +133,7 @@ public ImageSharpMiddleware(
127133
IEnumerable<IImageProvider> resolvers,
128134
IEnumerable<IImageWebProcessor> processors,
129135
IImageCache cache,
136+
ICacheKey cacheKey,
130137
ICacheHash cacheHash,
131138
CommandParser commandParser,
132139
FormatUtilities formatUtilities,
@@ -139,6 +146,7 @@ public ImageSharpMiddleware(
139146
Guard.NotNull(resolvers, nameof(resolvers));
140147
Guard.NotNull(processors, nameof(processors));
141148
Guard.NotNull(cache, nameof(cache));
149+
Guard.NotNull(cacheKey, nameof(cacheKey));
142150
Guard.NotNull(cacheHash, nameof(cacheHash));
143151
Guard.NotNull(commandParser, nameof(commandParser));
144152
Guard.NotNull(formatUtilities, nameof(formatUtilities));
@@ -150,6 +158,7 @@ public ImageSharpMiddleware(
150158
this.providers = resolvers as IImageProvider[] ?? resolvers.ToArray();
151159
this.processors = processors as IImageWebProcessor[] ?? processors.ToArray();
152160
this.cache = cache;
161+
this.cacheKey = cacheKey;
153162
this.cacheHash = cacheHash;
154163
this.commandParser = commandParser;
155164
this.parserCulture = this.options.UseInvariantParsingCulture
@@ -259,21 +268,21 @@ private async Task ProcessRequestAsync(
259268
ImageContext imageContext,
260269
CommandCollection commands)
261270
{
262-
// Create a cache key based on all the components of the requested url
263-
string uri = GetUri(context, commands);
264-
string key = this.cacheHash.Create(uri, this.options.CachedNameLength);
271+
// Create a cache key and hash
272+
string cacheKey = this.cacheKey.Create(context, commands);
273+
string cacheHash = this.cacheHash.Create(cacheKey, this.options.CachedNameLength);
265274

266275
// Check the cache, if present, not out of date and not requiring an update
267276
// we'll simply serve the file from there.
268277
ImageWorkerResult readResult = default;
269-
using (await this.asyncKeyLock.ReaderLockAsync(key))
278+
using (await this.asyncKeyLock.ReaderLockAsync(cacheHash))
270279
{
271-
readResult = await this.IsNewOrUpdatedAsync(sourceImageResolver, key);
280+
readResult = await this.IsNewOrUpdatedAsync(sourceImageResolver, cacheHash);
272281
}
273282

274283
if (!readResult.IsNewOrUpdated)
275284
{
276-
await this.SendResponseAsync(imageContext, key, readResult.CacheImageMetadata, readResult.Resolver, null);
285+
await this.SendResponseAsync(imageContext, cacheHash, readResult.CacheImageMetadata, readResult.Resolver, null);
277286
return;
278287
}
279288

@@ -285,7 +294,7 @@ private async Task ProcessRequestAsync(
285294
RecyclableMemoryStream outStream = null;
286295
try
287296
{
288-
Task<IDisposable> takeLockTask = this.asyncKeyLock.WriterLockAsync(key);
297+
Task<IDisposable> takeLockTask = this.asyncKeyLock.WriterLockAsync(cacheHash);
289298
bool lockWasAlreadyHeld = takeLockTask.Status != TaskStatus.RanToCompletion;
290299
using (await takeLockTask)
291300
{
@@ -294,7 +303,7 @@ private async Task ProcessRequestAsync(
294303
// the cache one more time
295304
if (lockWasAlreadyHeld)
296305
{
297-
readResult = await this.IsNewOrUpdatedAsync(sourceImageResolver, key);
306+
readResult = await this.IsNewOrUpdatedAsync(sourceImageResolver, cacheHash);
298307
}
299308

300309
if (readResult.IsNewOrUpdated)
@@ -356,12 +365,12 @@ private async Task ProcessRequestAsync(
356365
outStream.Length);
357366

358367
// Save the image to the cache and send the response to the caller.
359-
await this.cache.SetAsync(key, outStream, cachedImageMetadata);
368+
await this.cache.SetAsync(cacheHash, outStream, cachedImageMetadata);
360369
outStream.Position = 0;
361370

362371
// Remove any resolver from the cache so we always resolve next request
363372
// for the same key.
364-
CacheResolverLru.TryRemove(key);
373+
CacheResolverLru.TryRemove(cacheHash);
365374

366375
readResult = new ImageWorkerResult(cachedImageMetadata, null);
367376
}
@@ -375,7 +384,7 @@ private async Task ProcessRequestAsync(
375384
}
376385
}
377386

378-
await this.SendResponseAsync(imageContext, key, readResult.CacheImageMetadata, readResult.Resolver, outStream);
387+
await this.SendResponseAsync(imageContext, cacheHash, readResult.CacheImageMetadata, readResult.Resolver, outStream);
379388
}
380389
finally
381390
{
@@ -507,26 +516,5 @@ private async Task SendResponseAsync(
507516
throw exception;
508517
}
509518
}
510-
511-
private static string GetUri(HttpContext context, CommandCollection commands)
512-
{
513-
var sb = new StringBuilder(context.Request.Host.ToString());
514-
515-
string pathBase = context.Request.PathBase.ToString();
516-
if (!string.IsNullOrWhiteSpace(pathBase))
517-
{
518-
sb.AppendFormat("{0}/", pathBase);
519-
}
520-
521-
string path = context.Request.Path.ToString();
522-
if (!string.IsNullOrWhiteSpace(path))
523-
{
524-
sb.Append(path);
525-
}
526-
527-
sb.Append(QueryString.Create(commands));
528-
529-
return sb.ToString().ToLowerInvariant();
530-
}
531519
}
532520
}

0 commit comments

Comments
 (0)