Skip to content

Commit a3f306c

Browse files
Merge pull request #353 from Lombiq/issue/OCORE-136
Cache folder configuration for Azure and AWS Image Caches (Lombiq Technologies: OCORE-136)
2 parents 6096704 + ff7fdc4 commit a3f306c

9 files changed

+139
-6
lines changed

src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs

+11-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class AWSS3StorageCache : IImageCache
1717
{
1818
private readonly IAmazonS3 amazonS3Client;
1919
private readonly string bucketName;
20+
private readonly string cacheFolder;
2021

2122
/// <summary>
2223
/// Initializes a new instance of the <see cref="AWSS3StorageCache"/> class.
@@ -28,17 +29,21 @@ public AWSS3StorageCache(IOptions<AWSS3StorageCacheOptions> cacheOptions)
2829
AWSS3StorageCacheOptions options = cacheOptions.Value;
2930
this.bucketName = options.BucketName;
3031
this.amazonS3Client = AmazonS3ClientFactory.CreateClient(options);
32+
this.cacheFolder = string.IsNullOrEmpty(options.CacheFolder)
33+
? string.Empty
34+
: options.CacheFolder.Trim().Trim('/') + '/';
3135
}
3236

3337
/// <inheritdoc/>
3438
public async Task<IImageCacheResolver?> GetAsync(string key)
3539
{
36-
GetObjectMetadataRequest request = new() { BucketName = this.bucketName, Key = key };
40+
string keyWithFolder = this.GetKeyWithFolder(key);
41+
GetObjectMetadataRequest request = new() { BucketName = this.bucketName, Key = keyWithFolder };
3742
try
3843
{
3944
// HEAD request throws a 404 if not found.
4045
MetadataCollection metadata = (await this.amazonS3Client.GetObjectMetadataAsync(request)).Metadata;
41-
return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucketName, key, metadata);
46+
return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucketName, keyWithFolder, metadata);
4247
}
4348
catch
4449
{
@@ -52,7 +57,7 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
5257
PutObjectRequest request = new()
5358
{
5459
BucketName = this.bucketName,
55-
Key = key,
60+
Key = this.GetKeyWithFolder(key),
5661
ContentType = metadata.ContentType,
5762
InputStream = stream,
5863
AutoCloseStream = false
@@ -118,6 +123,9 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
118123
return null;
119124
}
120125

126+
private string GetKeyWithFolder(string key)
127+
=> this.cacheFolder + key;
128+
121129
/// <summary>
122130
/// <see href="https://github.com/aspnet/AspNetIdentity/blob/b7826741279450c58b230ece98bd04b4815beabf/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs"/>
123131
/// </summary>

src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions
1414
/// <inheritdoc/>
1515
public string BucketName { get; set; } = null!;
1616

17+
/// <summary>
18+
/// Gets or sets the cache folder's name that'll store cache files under the configured bucket.
19+
/// </summary>
20+
public string? CacheFolder { get; set; }
21+
1722
/// <inheritdoc/>
1823
public string? AccessKey { get; set; }
1924

src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Web.Caching.Azure;
1616
public class AzureBlobStorageCache : IImageCache
1717
{
1818
private readonly BlobContainerClient container;
19+
private readonly string cacheFolder;
1920

2021
/// <summary>
2122
/// Initializes a new instance of the <see cref="AzureBlobStorageCache"/> class.
@@ -27,12 +28,15 @@ public AzureBlobStorageCache(IOptions<AzureBlobStorageCacheOptions> cacheOptions
2728
AzureBlobStorageCacheOptions options = cacheOptions.Value;
2829

2930
this.container = new BlobContainerClient(options.ConnectionString, options.ContainerName);
31+
this.cacheFolder = string.IsNullOrEmpty(options.CacheFolder)
32+
? string.Empty
33+
: options.CacheFolder.Trim().Trim('/') + '/';
3034
}
3135

3236
/// <inheritdoc/>
3337
public async Task<IImageCacheResolver?> GetAsync(string key)
3438
{
35-
BlobClient blob = this.container.GetBlobClient(key);
39+
BlobClient blob = this.GetBlob(key);
3640

3741
if (!await blob.ExistsAsync())
3842
{
@@ -45,7 +49,7 @@ public AzureBlobStorageCache(IOptions<AzureBlobStorageCacheOptions> cacheOptions
4549
/// <inheritdoc/>
4650
public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
4751
{
48-
BlobClient blob = this.container.GetBlobClient(key);
52+
BlobClient blob = this.GetBlob(key);
4953

5054
BlobHttpHeaders headers = new()
5155
{
@@ -79,4 +83,7 @@ public static Response<BlobContainerInfo> CreateIfNotExists(
7983
AzureBlobStorageCacheOptions options,
8084
PublicAccessType accessType)
8185
=> new BlobContainerClient(options.ConnectionString, options.ContainerName).CreateIfNotExists(accessType);
86+
87+
private BlobClient GetBlob(string key)
88+
=> this.container.GetBlobClient(this.cacheFolder + key);
8289
}

src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@ public class AzureBlobStorageCacheOptions
1616

1717
/// <summary>
1818
/// Gets or sets the Azure Blob Storage container name.
19-
/// Must conform to Azure Blob Storage container naming guidlines.
19+
/// Must conform to Azure Blob Storage container naming guidelines.
2020
/// <see href="https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names"/>
2121
/// </summary>
2222
public string ContainerName { get; set; } = null!;
23+
24+
/// <summary>
25+
/// Gets or sets the cache folder's name that'll store cache files under the configured container.
26+
/// Must conform to Azure Blob Storage directory naming guidelines.
27+
/// <see href="https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#directory-names"/>
28+
/// </summary>
29+
public string? CacheFolder { get; set; }
2330
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using SixLabors.ImageSharp.Web.Tests.TestUtilities;
5+
using Xunit.Abstractions;
6+
7+
namespace SixLabors.ImageSharp.Web.Tests.Processing;
8+
9+
public class AWSS3StorageCacheCacheFolderServerTests : ServerTestBase<AWSS3StorageCacheCacheFolderTestServerFixture>
10+
{
11+
public AWSS3StorageCacheCacheFolderServerTests(AWSS3StorageCacheCacheFolderTestServerFixture fixture, ITestOutputHelper outputHelper)
12+
: base(fixture, outputHelper, TestConstants.AWSTestImage)
13+
{
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using SixLabors.ImageSharp.Web.Tests.TestUtilities;
5+
using Xunit.Abstractions;
6+
7+
namespace SixLabors.ImageSharp.Web.Tests.Processing;
8+
9+
public class AzureBlobStorageCacheCacheFolderServerTests : ServerTestBase<AzureBlobStorageCacheCacheFolderTestServerFixture>
10+
{
11+
public AzureBlobStorageCacheCacheFolderServerTests(AzureBlobStorageCacheCacheFolderTestServerFixture fixture, ITestOutputHelper outputHelper)
12+
: base(fixture, outputHelper, TestConstants.AzureTestImage)
13+
{
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using Amazon.S3;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using SixLabors.ImageSharp.Web.Caching.AWS;
7+
using SixLabors.ImageSharp.Web.DependencyInjection;
8+
using SixLabors.ImageSharp.Web.Providers.AWS;
9+
10+
namespace SixLabors.ImageSharp.Web.Tests.TestUtilities;
11+
12+
public class AWSS3StorageCacheCacheFolderTestServerFixture : TestServerFixture
13+
{
14+
protected override void ConfigureCustomServices(IServiceCollection services, IImageSharpBuilder builder)
15+
=> builder
16+
.Configure<AWSS3StorageImageProviderOptions>(o =>
17+
o.S3Buckets.Add(
18+
new AWSS3BucketClientOptions
19+
{
20+
Endpoint = TestConstants.AWSEndpoint,
21+
BucketName = TestConstants.AWSBucketName,
22+
AccessKey = TestConstants.AWSAccessKey,
23+
AccessSecret = TestConstants.AWSAccessSecret,
24+
Region = TestConstants.AWSRegion,
25+
Timeout = TestConstants.AWSTimeout,
26+
}))
27+
.AddProvider(AWSS3StorageImageProviderFactory.Create)
28+
.Configure<AWSS3StorageCacheOptions>(o =>
29+
{
30+
o.Endpoint = TestConstants.AWSEndpoint;
31+
o.BucketName = TestConstants.AWSCacheBucketName;
32+
o.AccessKey = TestConstants.AWSAccessKey;
33+
o.AccessSecret = TestConstants.AWSAccessSecret;
34+
o.Region = TestConstants.AWSRegion;
35+
o.Timeout = TestConstants.AWSTimeout;
36+
o.CacheFolder = TestConstants.AWSCacheFolder;
37+
38+
AWSS3StorageCache.CreateIfNotExists(o, S3CannedACL.Private);
39+
})
40+
.SetCache<AWSS3StorageCache>();
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using Azure.Storage.Blobs.Models;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using SixLabors.ImageSharp.Web.Caching.Azure;
7+
using SixLabors.ImageSharp.Web.DependencyInjection;
8+
using SixLabors.ImageSharp.Web.Providers.Azure;
9+
10+
namespace SixLabors.ImageSharp.Web.Tests.TestUtilities;
11+
12+
public class AzureBlobStorageCacheCacheFolderTestServerFixture : TestServerFixture
13+
{
14+
protected override void ConfigureCustomServices(IServiceCollection services, IImageSharpBuilder builder)
15+
=> builder
16+
.Configure<AzureBlobStorageImageProviderOptions>(o =>
17+
o.BlobContainers.Add(
18+
new AzureBlobContainerClientOptions
19+
{
20+
ConnectionString = TestConstants.AzureConnectionString,
21+
ContainerName = TestConstants.AzureContainerName
22+
}))
23+
.AddProvider(AzureBlobStorageImageProviderFactory.Create)
24+
.Configure<AzureBlobStorageCacheOptions>(o =>
25+
{
26+
o.ConnectionString = TestConstants.AzureConnectionString;
27+
o.ContainerName = TestConstants.AzureCacheContainerName;
28+
o.CacheFolder = TestConstants.AzureCacheFolder;
29+
30+
AzureBlobStorageCache.CreateIfNotExists(o, PublicAccessType.None);
31+
})
32+
.SetCache<AzureBlobStorageCache>();
33+
}

tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs

+2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ public static class TestConstants
88
public const string AzureConnectionString = "UseDevelopmentStorage=true";
99
public const string AzureContainerName = "azure";
1010
public const string AzureCacheContainerName = "is-cache";
11+
public const string AzureCacheFolder = "cache/folder";
1112
public const string AWSEndpoint = "http://localhost:4568/";
1213
public const string AWSRegion = "eu-west-2";
1314
public const string AWSBucketName = "aws";
1415
public const string AWSCacheBucketName = "aws-cache";
1516
public const string AWSAccessKey = "";
1617
public const string AWSAccessSecret = "";
18+
public const string AWSCacheFolder = "cache/folder";
1719
public const string ImagePath = "SubFolder/sîxläbörs.îmägéshärp.wéb.png";
1820
public const string PhysicalTestImage = "http://localhost/" + ImagePath;
1921
public const string AzureTestImage = "http://localhost/" + AzureContainerName + "/" + ImagePath;

0 commit comments

Comments
 (0)