Привет,
Недавно я реализовал создание Captcha в своем API AWS Lambda .NET Core 2. Я мог бы поклясться, что когда-то он работал, но сейчас он просто выходит из строя. Я изучил текущую реализацию .NET Core System.Drawing; однако из того, что я читаю, явные зависимости от GDI + делают эту библиотеку несовместимой с разными платформами. При дальнейшем исследовании обнаруживается CompatCore.System.Drawing, который изначально был разработан командой Mono. Предположительно, это кроссплатформенная версия System.Drawing, способная использовать реализацию GDI + на базе Linux; однако попытка его использования просто вызывает следующую ошибку / трассировку стека:
System.TypeInitializationException: The type initializer for 'System.Drawing.GDIPlus' threw an exception. ---> System.DllNotFoundException: Unable to load DLL 'gdiplus': The specified module or one of its dependencies could not be found.
(Exception from HRESULT: 0x8007007E)
at System.Drawing.GDIPlus.GdiplusStartup(UInt64& token, GdiplusStartupInput& input, GdiplusStartupOutput& output)
at System.Drawing.GDIPlus..cctor()
--- End of inner exception stack trace ---
at System.Drawing.GDIPlus.GdipCreateBitmapFromScan0(Int32 width, Int32 height, Int32 stride, PixelFormat format, IntPtr scan0, IntPtr& bmp)
at System.Drawing.Bitmap..ctor(Int32 width, Int32 height, PixelFormat format)
at Ivy.Captcha.Services.CaptchaGenerationService.GenerateCaptchaImage(Int32 captchaCharLength, Int32 width, Int32 height)
at IAGE.Api.Data.Services.CaptchaApiService.GenerateCaptcha() in D:\Workspaces\iam-global-education\src\Api\IAGE.Api.Data\Services\CaptchaApiService.cs:line 63
at lambda_method.lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__22.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Elmah.Io.AspNetCore.ElmahIoMiddleware.d__6.MoveNext()
Согласно следующему потоку: https://github.com/CoreCompat/CoreCompat/issues/3
Я должен был иметь возможность включить libgdiplus DLL ref, добавив NuGet-пакет runtime.linux-x64.CoreCompat.System.Drawing; однако для меня это было бесполезно. Я пробовал различные комбинации библиотечных ссылок, надеясь, что это будет аналогичная проблема, которую многие люди видели с System.Cryptography.x509Certificates; но мне еще предстоит найти комбинацию, которая позволила бы этому работать.
Удалось ли кому-нибудь еще запустить функциональность System.Drawing на AWS Lambda?
Я слышал об использовании libgdiplus для System.Drawing в Linux, но не пробовал. Для обработки изображений в Lambda я использовал последнюю версию Magick.NET-Q16-AnyCPU . Я не рисовал с его помощью, который, как я полагаю, вам понадобится для Captcha, но я вижу, что у него есть некоторые возможности рисования .
Моя проблема заключается в том, что libgdiplus не является установкой по умолчанию в экземпляре Lambda Linux. Предположительно, runtime.linux-x64.CoreCompat.System.Drawing был пакетом NuGet, который просто выполнял сценарии для установки этой библиотеки через apt-get при запуске приложения; однако, покопавшись в коде этого проекта по приведенной ниже ссылке, я не смог точно понять, как это работает и почему у меня может возникнуть сбой.
Я продолжу искать решение пока; но эта ссылка Magick.NET, по-видимому, является допустимым методом создания некоторых изображений Captcha, поэтому я обязательно сохраню ее в качестве запасного варианта. Я рассмотрел возможность использования собственного System.Drawing, поскольку, похоже, он был разработан для лучшей кросс-платформенной совместимости, но все же кажется, что они каким-то образом ссылаются на установленную библиотеку GDI +, хотя,
Вы видели, как этот пакет Magick.NET успешно используется на Lambda?
Да, Magick.NET был обновлен через пару месяцев, чтобы сразу перейти к работе с кросс-платформой. Все, что мне нужно было сделать, это добавить его в проект. Ниже приведена лямбда-функция, которую я написал для создания с ее помощью эскизов изображений.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" />
<PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.1.0" />
<PackageReference Include="Amazon.Lambda.S3Events" Version="1.0.2" />
<PackageReference Include="AWSSDK.S3" Version="3.3.16.2" />
<PackageReference Include="AWSXRayRecorder.Handlers.AwsSdk" Version="2.1.0-beta" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.4.2" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Amazon.Lambda.Tools" Version="2.1.1" />
</ItemGroup>
</Project>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Amazon.Lambda.S3Events;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.XRay.Recorder.Core;
using ImageMagick;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
namespace ImageResizer
{
public class Function
{
IAmazonS3 S3Client { get; set; }
/// <summary>
/// Default constructor. This constructor is used by Lambda to construct the instance. When invoked in a Lambda environment
/// the AWS credentials will come from the IAM role associated with the function and the AWS region will be set to the
/// region the Lambda function is executed in.
/// </summary>
public Function()
{
Amazon.XRay.Recorder.Handlers.AwsSdk.AWSSDKHandler.RegisterXRayForAllServices();
S3Client = new AmazonS3Client();
}
/// <summary>
/// Constructs an instance with a preconfigured S3 client. This can be used for testing the outside of the Lambda environment.
/// </summary>
/// <param name="s3Client"></param>
public Function(IAmazonS3 s3Client)
{
this.S3Client = s3Client;
}
HashSet<string> SupportedImageTypes { get; } = new HashSet<string> { ".png", ".jpg", ".jpeg" };
/// <summary>
/// This method is called for every Lambda invocation. This method takes in an S3 event object and can be used
/// to respond to S3 notifications.
/// </summary>
/// <param name="evnt"></param>
/// <param name="context"></param>
/// <returns></returns>
public async Task FunctionHandlerAsync(S3Event evnt, ILambdaContext context)
{
foreach (var record in evnt.Records)
{
var bucket = record.S3.Bucket.Name;
var key = record.S3.Object.Key;
if (key.StartsWith("thumbnails"))
{
context.Logger.LogLine($"Object s3://{bucket}/{key} is already a thumbnail");
continue;
}
if (!SupportedImageTypes.Contains(Path.GetExtension(key.ToLower())))
{
context.Logger.LogLine($"Object s3://{bucket}/{key} is not a supported image type");
continue;
}
context.Logger.LogLine($"Processing s3://{bucket}/{key}");
MemoryStream resizedImageStream;
using (var response = await this.S3Client.GetObjectAsync(bucket, key))
{
AWSXRayRecorder.Instance.BeginSubsegment("Magick Image Resize");
try
{
using (MagickImageCollection collection = new MagickImageCollection(response.ResponseStream))
{
foreach (MagickImage image in collection)
{
image.Resize(200, 200);
}
context.Logger.LogLine($" Image resized");
resizedImageStream = new MemoryStream();
collection.Write(resizedImageStream);
resizedImageStream.Position = 0;
}
}
catch(Exception e)
{
AWSXRayRecorder.Instance.AddException(e);
throw;
}
finally
{
AWSXRayRecorder.Instance.EndSubsegment();
}
}
var index = key.LastIndexOf('/');
var thumbnailKey = "thumbnails/" + (index != -1 ? key.Substring(index + 1) : key);
await this.S3Client.PutObjectAsync(new PutObjectRequest
{
BucketName = bucket,
Key = thumbnailKey,
InputStream = resizedImageStream
});
context.Logger.LogLine($" Thumbnail saved to s3://{bucket}/{thumbnailKey}");
}
}
}
}
Я только начинаю заниматься магией и думаю, что это прекрасно сработает. Мало того, что у вас есть доступ к относительно разнообразному API для работы с изображениями, он также хорошо поддерживается кросс-платформенным. Большое спасибо за предложение, еще немного усилий по разработке, и я смогу преодолеть это препятствие без проблем!
Мне все еще любопытно, как некоторые люди смогли запустить и запустить реализацию GDI +. Я не вижу способа, которым можно было бы установить соответствующую библиотеку libgdiplus в Lambda до выполнения во время выполнения.
Похоже, что API ImageMagick Drawables может некорректно быть кроссплатформенным с AWS Lambda из того, что я вижу. После реализации библиотеки и выполнения функции Draw () возникает следующая ошибка:
ImageMagick.MagickDrawErrorException: NonconformingDrawingPrimitiveDefinition `text' @ error/draw.c/DrawImage/3402
at ImageMagick.MagickExceptionHelper.Check(IntPtr exception)
at ImageMagick.DrawingWand.NativeDrawingWand.Render()
at ImageMagick.MagickImage.Draw(IEnumerable`1 drawables)
at Ivy.Captcha.Magick.Services.MagickCaptchaGenerationService.GenerateCaptchaImage(Int32 captchaCharLength, Int32 width, Int32 height)
at IAGE.Api.Data.Services.CaptchaApiService.GenerateCaptcha() in D:\Workspaces\iam-global-education\src\Api\IAGE.Api.Data\Services\CaptchaApiService.cs:line 63
at lambda_method.lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__22.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Elmah.Io.AspNetCore.ElmahIoMiddleware.d__6.MoveNext()
Независимо от того, что я делаю, мне кажется, что никакие функции рисования не работают в Lambda. Я пытался использовать System.Drawing (Microsoft), CoreCompat.System.Drawing (Mono) и ImageMagick (3rd Party), но все, казалось, безрезультатно. Кажется, что каждая библиотека генерации изображений имеет некоторые основные системные зависимости, которые мне трудно обойти с помощью AWS Lambda. Я продолжу экспериментировать и сообщу, если добьюсь успеха.
Есть ли шанс, что мы когда-нибудь сможем настроить нашу среду выполнения Lambda в будущем, подобно тому, как мы можем настраивать контейнеры Docker? Все это можно решить, просто установив libgdiplus в мой экземпляр Lambda.
Я идиот, и мне никогда не следовало создавать изображения Captcha на моем сервере AWS Lambda, когда я мог бы просто использовать Google ReCaptcha. Таким образом, моя AWS Lambda обрабатывает только проверочный код и никогда не ссылается на какие-либо функции рисования.
Я все еще хотел бы увидеть метод реализации какой-либо формы функции рисования на AWS Lambda в будущем; однако я собираюсь отложить эту проблему на второй план и списать это на ужасное дизайнерское решение с моей стороны.
Возможно, уровни развертывания Lambda могут решить эту проблему до тех пор, пока в какой-то момент в .Net Core не появится надлежащая поддержка.
https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html
Самый полезный комментарий
Да, Magick.NET был обновлен через пару месяцев, чтобы сразу перейти к работе с кросс-платформой. Все, что мне нужно было сделать, это добавить его в проект. Ниже приведена лямбда-функция, которую я написал для создания с ее помощью эскизов изображений.