Skip to content

Commit

Permalink
Merge pull request #44 from AlexMedia/main
Browse files Browse the repository at this point in the history
Add ClientFactory overload to S3 plugin
  • Loading branch information
lilith authored Jan 13, 2021
2 parents 83ee779 + defba59 commit c643e21
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 31 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ namespace Imageflow.Server.Example
// You can call AddImageflowS3Service multiple times for each unique access key
services.AddImageflowS3Service(new S3ServiceOptions( null,null)
.MapPrefix("/ri/", RegionEndpoint.USEast1, "resizer-images")
.MapPrefix("/imageflow-resources/", RegionEndpoint.USWest2, "imageflow-resources"));
.MapPrefix("/imageflow-resources/", RegionEndpoint.USWest2, "imageflow-resources")
.MapPrefix("/custom-s3client/", () => new AmazonS3Client(), "custom-client", "", false, false)
);

// Make Azure container available at /azure
// You can call AddImageflowAzureBlobService multiple times for each connection string
Expand Down
5 changes: 4 additions & 1 deletion examples/Imageflow.Server.Example/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Extensions.Hosting;
using System;
using System.IO;
using Amazon.S3;
using Imageflow.Server.HybridCache;

namespace Imageflow.Server.Example
Expand Down Expand Up @@ -45,7 +46,9 @@ public void ConfigureServices(IServiceCollection services)
// You can call AddImageflowS3Service multiple times for each unique access key
services.AddImageflowS3Service(new S3ServiceOptions( null,null)
.MapPrefix("/ri/", RegionEndpoint.USEast1, "resizer-images")
.MapPrefix("/imageflow-resources/", RegionEndpoint.USWest2, "imageflow-resources"));
.MapPrefix("/imageflow-resources/", RegionEndpoint.USWest2, "imageflow-resources")
.MapPrefix("/custom-s3client/", () => new AmazonS3Client(), "custom-client", "", false, false)
);

// Make Azure container available at /azure
// You can call AddImageflowAzureBlobService multiple times for each connection string
Expand Down
3 changes: 2 additions & 1 deletion src/Imageflow.Server.Storage.S3/PrefixMapping.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System;
using Amazon.S3;

namespace Imageflow.Server.Storage.S3
{
internal struct PrefixMapping
{
internal string Prefix;
internal AmazonS3Config Config;
internal Func<IAmazonS3> ClientFactory;
internal string Bucket;
internal string BlobPrefix;
internal bool IgnorePrefixCase;
Expand Down
14 changes: 1 addition & 13 deletions src/Imageflow.Server.Storage.S3/S3Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.S3;
using Imazen.Common.Storage;
using Microsoft.Extensions.Logging;
Expand All @@ -13,19 +12,8 @@ public class S3Service : IBlobProvider
{
private readonly List<PrefixMapping> mappings = new List<PrefixMapping>();

private readonly AWSCredentials credentials;
public S3Service(S3ServiceOptions options, ILogger<S3Service> logger)
{

if (options.AccessKeyId == null)
{
credentials = new AnonymousAWSCredentials();
}
else
{
credentials = new BasicAWSCredentials(options.AccessKeyId, options.SecretAccessKey);
}

foreach (var m in options.Mappings)
{
mappings.Add(m);;
Expand Down Expand Up @@ -66,7 +54,7 @@ public async Task<IBlobData> Fetch(string virtualPath)

try
{
using var client = new AmazonS3Client(credentials, mapping.Config);
using var client = mapping.ClientFactory();
var req = new Amazon.S3.Model.GetObjectRequest() { BucketName = mapping.Bucket, Key = key };

var s = await client.GetObjectAsync(req);
Expand Down
59 changes: 44 additions & 15 deletions src/Imageflow.Server.Storage.S3/S3ServiceOptions.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;

namespace Imageflow.Server.Storage.S3
{
public class S3ServiceOptions
{

internal readonly string AccessKeyId;
internal readonly string SecretAccessKey;
private readonly AWSCredentials credentials;
internal readonly List<PrefixMapping> Mappings = new List<PrefixMapping>();

public S3ServiceOptions()
{
credentials = new AnonymousAWSCredentials();
}

public S3ServiceOptions(AWSCredentials credentials)
{
this.credentials = credentials;
}

public S3ServiceOptions(string accessKeyId, string secretAccessKey)
{
this.AccessKeyId = accessKeyId;
this.SecretAccessKey = secretAccessKey;
credentials = accessKeyId == null
? (AWSCredentials) new AnonymousAWSCredentials()
: new BasicAWSCredentials(accessKeyId, secretAccessKey);
}

public S3ServiceOptions MapPrefix(string prefix, RegionEndpoint region, string bucket)
Expand All @@ -30,9 +39,9 @@ public S3ServiceOptions MapPrefix(string prefix, RegionEndpoint region, string b

public S3ServiceOptions MapPrefix(string prefix, RegionEndpoint region, string bucket, string blobPrefix,
bool ignorePrefixCase, bool lowercaseBlobPath)
=> MapPrefix(prefix, new AmazonS3Config() {RegionEndpoint = region}, bucket,
=> MapPrefix(prefix, new AmazonS3Config() { RegionEndpoint = region }, bucket,
blobPrefix, ignorePrefixCase, lowercaseBlobPath);

/// <summary>
/// Maps a given prefix to a specified location within a bucket
/// </summary>
Expand All @@ -46,7 +55,27 @@ public S3ServiceOptions MapPrefix(string prefix, RegionEndpoint region, string b
/// (requires that actual blobs all be lowercase).</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public S3ServiceOptions MapPrefix(string prefix, AmazonS3Config s3Config, string bucket, string blobPrefix, bool ignorePrefixCase, bool lowercaseBlobPath)
public S3ServiceOptions MapPrefix(string prefix, AmazonS3Config s3Config, string bucket, string blobPrefix,
bool ignorePrefixCase, bool lowercaseBlobPath)
{
Func<IAmazonS3> client = () => new AmazonS3Client(credentials, s3Config);
return MapPrefix(prefix, client, bucket, blobPrefix, ignorePrefixCase, lowercaseBlobPath);
}

/// <summary>
/// Maps a given prefix to a specified location within a bucket
/// </summary>
/// <param name="prefix">The prefix to capture image requests within</param>
/// <param name="s3ClientFactory">Lambda function to provide an instance of IAmazonS3, which will be disposed after use.</param>
/// <param name="bucket">The bucket to serve images from</param>
/// <param name="blobPrefix">The path within the bucket to serve images from. Can be an empty string to serve
/// from root of bucket.</param>
/// <param name="ignorePrefixCase">Whether to be cases sensitive about requests matching 'prefix'</param>
/// <param name="lowercaseBlobPath">Whether to lowercase all incoming paths to allow for case insensitivity
/// (requires that actual blobs all be lowercase).</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public S3ServiceOptions MapPrefix(string prefix, Func<IAmazonS3> s3ClientFactory, string bucket, string blobPrefix, bool ignorePrefixCase, bool lowercaseBlobPath)
{
prefix = prefix.TrimStart('/').TrimEnd('/');
if (prefix.Length == 0)
Expand All @@ -59,16 +88,16 @@ public S3ServiceOptions MapPrefix(string prefix, AmazonS3Config s3Config, string

Mappings.Add(new PrefixMapping()
{
Bucket=bucket,
Prefix=prefix,
Config=s3Config,
Bucket = bucket,
Prefix = prefix,
ClientFactory = s3ClientFactory,
BlobPrefix = blobPrefix,
IgnorePrefixCase = ignorePrefixCase,
LowercaseBlobPath = lowercaseBlobPath

});
return this;
}

}
}
52 changes: 52 additions & 0 deletions tests/Imageflow.Server.Tests/IntegrationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Runtime.CompilerServices;
using System.Threading;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Imageflow.Fluent;
using Imageflow.Server.DiskCache;
using Imageflow.Server.Storage.RemoteReader;
Expand Down Expand Up @@ -254,6 +256,56 @@ public async void TestAmazonS3()
}
}

[Fact]
public async void TestAmazonS3WithCustomClient()
{
using (var contentRoot = new TempContentRoot()
.AddResource("images/fire.jpg", "TestFiles.fire-umbrella-small.jpg")
.AddResource("images/logo.png", "TestFiles.imazen_400.png"))
{

var diskCacheDir = Path.Combine(contentRoot.PhysicalPath, "diskcache");
var hostBuilder = new HostBuilder()
.ConfigureServices(services =>
{
services.AddImageflowDiskCache(new DiskCacheOptions(diskCacheDir) {AsyncWrites = false});
services.AddImageflowS3Service(
new S3ServiceOptions()
.MapPrefix("/ri/", () => new AmazonS3Client(new AnonymousAWSCredentials(), RegionEndpoint.USEast1), "resizer-images", "", false, false));
})
.ConfigureWebHost(webHost =>
{
// Add TestServer
webHost.UseTestServer();
webHost.Configure(app =>
{
app.UseImageflow(new ImageflowMiddlewareOptions()
.SetMapWebRoot(false)
.SetAllowDiskCaching(true)
// Maps / to ContentRootPath/images
.MapPath("/", Path.Combine(contentRoot.PhysicalPath, "images")));
});
});

// Build and start the IHost
using var host = await hostBuilder.StartAsync();

// Create an HttpClient to send requests to the TestServer
using var client = host.GetTestClient();

using var response = await client.GetAsync("/ri/not_there.jpg");
Assert.Equal(HttpStatusCode.NotFound,response.StatusCode);

using var response2 = await client.GetAsync("/ri/imageflow-icon.png?width=1");
response2.EnsureSuccessStatusCode();

await host.StopAsync(CancellationToken.None);

var cacheFiles = Directory.GetFiles(diskCacheDir, "*.png", SearchOption.AllDirectories);
Assert.Single(cacheFiles);
}
}

[Fact]
public async void TestPresetsExclusive()
{
Expand Down

0 comments on commit c643e21

Please sign in to comment.