Skip to content

Commit

Permalink
Initial work on adding AWS S3 Provider
Browse files Browse the repository at this point in the history
  • Loading branch information
andymac4182 committed Sep 8, 2019
1 parent 0e08491 commit 4ce44fb
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 1 deletion.
15 changes: 15 additions & 0 deletions ImageSharp.Web.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Web.Benchmarks",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Web.Providers.Azure", "src\ImageSharp.Web.Providers.Azure\ImageSharp.Web.Providers.Azure.csproj", "{E2A545EC-B909-4EAD-B95F-397F68588BE3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Web.Providers.AWS", "src\ImageSharp.Web.Providers.AWS\ImageSharp.Web.Providers.AWS.csproj", "{E631D300-ACD5-40EA-A6BB-08E22092EC76}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -111,6 +113,18 @@ Global
{E2A545EC-B909-4EAD-B95F-397F68588BE3}.Release|x64.Build.0 = Release|Any CPU
{E2A545EC-B909-4EAD-B95F-397F68588BE3}.Release|x86.ActiveCfg = Release|Any CPU
{E2A545EC-B909-4EAD-B95F-397F68588BE3}.Release|x86.Build.0 = Release|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Debug|x64.ActiveCfg = Debug|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Debug|x64.Build.0 = Debug|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Debug|x86.ActiveCfg = Debug|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Debug|x86.Build.0 = Debug|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Release|Any CPU.Build.0 = Release|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Release|x64.ActiveCfg = Release|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Release|x64.Build.0 = Release|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Release|x86.ActiveCfg = Release|Any CPU
{E631D300-ACD5-40EA-A6BB-08E22092EC76}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -122,6 +136,7 @@ Global
{BD73DBBD-6859-44C2-99FC-84148A6239A5} = {EA4178FC-11E3-496A-B630-0680B35E0AF8}
{0B15E490-7821-42DF-86A5-4DEAE921DE59} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{E2A545EC-B909-4EAD-B95F-397F68588BE3} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{E631D300-ACD5-40EA-A6BB-08E22092EC76} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C5B38B65-A19E-4359-859C-5B2205429BD1}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyTitle>SixLabors.ImageSharp.Web.Providers.AWS</AssemblyTitle>
<Authors>Six Labors and contributors</Authors>
<Company>Six Labors</Company>
<Copyright>Copyright (c) Six Labors and contributors.</Copyright>
<Product>SixLabors.ImageSharp.Web.Providers.AWS</Product>
<Description>A provider for resolving images via AWS S3.</Description>
<NeutralLanguage>en</NeutralLanguage>

<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.3</LangVersion>

<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>SixLabors.ImageSharp.Web.Providers.AWS</AssemblyName>
<PackageId>SixLabors.ImageSharp.Web.Providers.AWS</PackageId>
<PackageTags>Image Middleware Resize Crop Gif Jpg Jpeg Bitmap Png Azure</PackageTags>
<PackageIconUrl>https://raw.githubusercontent.com/SixLabors/Branding/master/icons/imagesharp/sixlabors.imagesharp.128.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/SixLabors/ImageSharp.Web</PackageProjectUrl>
<PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/SixLabors/ImageSharp.Web</RepositoryUrl>

<DebugType Condition="$(codecov) != ''">full</DebugType>
<DebugType Condition="$(codecov) == ''">portable</DebugType>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.S3" Version="3.3.104.23" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<Compile Include="..\Shared\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ImageSharp.Web\ImageSharp.Web.csproj" />
</ItemGroup>

<PropertyGroup>
<CodeAnalysisRuleSet>..\..\shared-infrastructure\SixLabors.ruleset</CodeAnalysisRuleSet>
<RootNamespace>SixLabors.ImageSharp.Web</RootNamespace>
</PropertyGroup>

<ItemGroup>
<AdditionalFiles Include="..\..\shared-infrastructure\stylecop.json" />
</ItemGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Util;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp.Web.Resolvers;

namespace SixLabors.ImageSharp.Web.Providers
{
/// <summary>
/// Returns images stored in AWS S3.
/// </summary>
public class AWSS3StorageImageProvider : IImageProvider
{
/// <summary>
/// Character array to remove from paths.
/// </summary>
private static readonly char[] SlashChars = { '\\', '/' };

private readonly IAmazonS3 amazonS3Client;
private readonly ILogger<AWSS3StorageImageProvider> logger;
private readonly AWSS3StorageImageProviderOptions storageOptions;
private Func<HttpContext, bool> match;

/// <summary>
/// Initializes a new instance of the <see cref="AWSS3StorageImageProvider"/> class.
/// </summary>
/// <param name="amazonS3Client">Amazon S3 client</param>
/// <param name="logger">Microsoft.Extensions.Logging ILogger</param>
/// <param name="storageOptions">The S3 storage options</param>
public AWSS3StorageImageProvider(IAmazonS3 amazonS3Client, ILogger<AWSS3StorageImageProvider> logger, IOptions<AWSS3StorageImageProviderOptions> storageOptions)
{
Guard.NotNull(storageOptions, nameof(storageOptions));

this.amazonS3Client = amazonS3Client;
this.logger = logger;
this.storageOptions = storageOptions.Value;
}

/// <inheritdoc />
public Func<HttpContext, bool> Match
{
get => this.match ?? this.IsMatch;
set => this.match = value;
}

/// <inheritdoc />
public bool IsValidRequest(HttpContext context)
{
var displayUrl = context.Request.Path;
return Path.GetExtension(displayUrl).EndsWith(".jpg") || Path.GetExtension(displayUrl).EndsWith(".png");
}

/// <inheritdoc />
public async Task<IImageResolver> GetAsync(HttpContext context)
{
PathString displayUrl = context.Request.Path;
this.logger.LogDebug("Getting image for {ImageUri}", displayUrl);
string imageId = Path.GetFileNameWithoutExtension(displayUrl);
this.logger.LogDebug("Image id is {ImageId}", imageId);

string imagePath = $"{imageId}.png";

bool imageExists = await this.KeyExists(this.storageOptions.BucketName, imagePath);

if (!imageExists)
{
this.logger.LogDebug("No image found for {ImageId}", imageId);
return null;
}

this.logger.LogDebug("Found image {ImageId}", imageId);

return new AWSS3FileSystemResolver(this.amazonS3Client, this.storageOptions.BucketName, imagePath);
}

private bool IsMatch(HttpContext context)
{
string path = context.Request.Path.Value.TrimStart(SlashChars);
return path.StartsWith(this.storageOptions.BucketName, StringComparison.OrdinalIgnoreCase);
}

// ref https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Services/S3/Custom/_bcl/IO/S3FileInfo.cs#L118
private async Task<bool> KeyExists(string bucketName, string key, CancellationToken cancellationToken = default(CancellationToken))
{
this.logger.LogDebug("Checking for the existence of key {Key} in bucket {BucketName}", key, bucketName);

try
{
var request = new GetObjectMetadataRequest
{
BucketName = bucketName,
Key = key
};

((Amazon.Runtime.Internal.IAmazonWebServiceRequest)request)
.AddBeforeRequestHandler(FileIORequestEventHandler);

// If the object doesn't exist then a "NotFound" will be thrown
await this.amazonS3Client.GetObjectMetadataAsync(request, cancellationToken).ConfigureAwait(false);
return true;
}
catch (AmazonS3Exception e)
{
if (string.Equals(e.ErrorCode, "NoSuchBucket"))
{
return false;
}

if (string.Equals(e.ErrorCode, "NotFound"))
{
return false;
}

throw;
}
}

private static void FileIORequestEventHandler(object sender, RequestEventArgs args)
{
if (args is WebServiceRequestEventArgs wsArgs)
{
string currentUserAgent = wsArgs.Headers[AWSSDKUtils.UserAgentHeader];
wsArgs.Headers[AWSSDKUtils.UserAgentHeader] = currentUserAgent + " FileIO";
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

namespace SixLabors.ImageSharp.Web.Providers
{
/// <summary>
/// Configuration options for the <see cref="AWSS3StorageImageProvider"/> provider.
/// </summary>
public class AWSS3StorageImageProviderOptions
{
/// <summary>
/// Gets or sets the bucket name.
/// Must conform to AWS S3 bucket naming guidelines.
/// </summary>
public string BucketName { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System.IO;
using System.Threading.Tasks;
using Amazon.S3;
using Amazon.S3.Model;

namespace SixLabors.ImageSharp.Web.Resolvers
{
/// <summary>
/// Provides means to manage image buffers within the AWS S3 file system.
/// </summary>
public class AWSS3FileSystemResolver : IImageResolver
{
private readonly IAmazonS3 amazonS3;
private readonly string bucketName;
private readonly string imagePath;

/// <summary>
/// Initializes a new instance of the <see cref="AWSS3FileSystemResolver"/> class.
/// </summary>
/// <param name="amazonS3">Amazon S3 Client</param>
/// <param name="bucketName">Bucket Name for where the files are</param>
/// <param name="imagePath">S3 Key</param>
public AWSS3FileSystemResolver(IAmazonS3 amazonS3, string bucketName, string imagePath)
{
this.amazonS3 = amazonS3;
this.bucketName = bucketName;
this.imagePath = imagePath;
}

/// <inheritdoc />
public async Task<ImageMetadata> GetMetaDataAsync()
{
GetObjectMetadataResponse metadata = await this.amazonS3.GetObjectMetadataAsync(this.bucketName, this.imagePath);
return new ImageMetadata(metadata.LastModified);
}

/// <inheritdoc />
public async Task<Stream> OpenReadAsync()
{
GetObjectResponse s3Object = await this.amazonS3.GetObjectAsync(this.bucketName, this.imagePath);
return s3Object.ResponseStream;
}
}
}
3 changes: 2 additions & 1 deletion src/Shared/AssemblyInfo.Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
// Ensure the internals can be built and tested.
[assembly: InternalsVisibleTo("SixLabors.ImageSharp.Web.Tests")]
[assembly: InternalsVisibleTo("SixLabors.ImageSharp.Web.Providers.Azure")]
[assembly: InternalsVisibleTo("ImageSharp.Web.Benchmarks")]
[assembly: InternalsVisibleTo("SixLabors.ImageSharp.Web.Providers.AWS")]
[assembly: InternalsVisibleTo("ImageSharp.Web.Benchmarks")]

0 comments on commit 4ce44fb

Please sign in to comment.