#c# #asp.net-core #websocket #aws-lambda #aws-api-gateway
#c# #asp.net-ядро #websocket #aws-lambda #aws-api-gateway
Вопрос:
Я новичок в AWS Lambda, увлекательная штука. Я играю с некоторыми сценариями, интегрирующими AWS Lambda netcoreapp3.1
, разработанную с помощью AWS API Gateway. Я видел, что AWS Api Gateway поддерживает управление веб-сокетами, и это здорово, потому что он поддерживает открытые соединения и отправляет любой фрейм на серверную лямбду, а также предоставляет некоторую защищенную конечную точку POST для отправки сообщений из серверной лямбды клиенту, подключенному к веб-сокету шлюза. Вся тяжелая работа там.
Я также видел, что существует некоторый пакет для разработчиков C #, который позволяет использовать лямбда-приложения так, как если бы они были стандартным веб-api. См. https://aws.amazon.com/blogs/compute/announcing-aws-lambda-supports-for-net-core-3-1 Это фантастика, потому что я, честно говоря, не понимаю, почему некоторые разработчики всегда пытаются иметь одну лямбду на конечную точку, когда лямбда может использоваться для внутренней маршрутизации к любой конечной точке и использовать все преимущества универсального хостинга для aspnet core 3.1 плюс ведение журнала, контейнер для внедрения зависимостей из коробкии т.д. и очень хороший способ протестировать все, как если бы это было приложение web api, не беспокоясь о точке входа в лямбда. Смотрите пакет https://github.com/aws/aws-lambda-dotnet/tree/master/Libraries/src/Amazon .Лямбда.AspNetCoreServer
Точка входа lambda выглядит следующим образом. Как вы можете видеть, я просто наследую от APIGatewayProxyFunction
класса пакета, и все использует то же самое, как если бы я запускал его локально как веб-приложение kestrel.
public class LambdaEntryPoint
: APIGatewayProxyFunction
{
protected override void Init(IWebHostBuilder builder)
{
builder
.UseStartup<Startup>();
}
}
Program.cs
Локальная точка входа или при запуске как веб-приложение kestrel
public static class LocalEntryPoint
{
private static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(
webBuilder => { webBuilder.UseStartup<Startup>(); });
}
}
Startup.cs
Выглядит как любой стандартный запуск, с регистрациями и http-конвейером, и ничего не знает о том, выполняется ли он как lambda или как стандартное веб-приложение (здорово, не так ли? Если бы я только мог использовать это приятным способом для веб-сокетов и протестировать его …)
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
}
Having said that, I haven’t been lucky to find any example on how to use an AWS Lambda with AspNetCore for web socket connectivity.
Ideally I’d like my lambda application to be a web api with local and lambda entrypoint as described in that package (it has some nice samples) or by using the dotnet template called serverless.AspNetCoreWebAPI
, but where I could also use it to map web socket routes, so that the same lambda application works with API Gateway REST and API Gateway Websocket
I have seen some workarounds like this https://github.com/aws/aws-lambda-dotnet/issues/644#issuecomment-621528565 where the methods MarshallRequest
and PostMarshallRequestFeature
are overriden, but I don’t fully understand some things such as some class types and that brings me to the following questions:
- Can I have a lambda to serve both integration with REST and WebSocket Api Gateway? Is there any potential issue/recommendation to avoid it?
- How does API Gateway invoke the lambda function and how could I test this functionality? I’ve had a look at the https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool and configured it to debug it with rider, but I am absolutely lost when having to test some websocket input. How to do that?
Ta.
UPDATE 1 2021-02-23:
Thanks. As per the event payload samples here they are:
- When
$connect
withwscat -c wss://{api_id}.execute-api.eu-west-3.amazonaws.com/Prod
the APIGatewayProxyRequest is:{ "Resource": null, "Path": null, "HttpMethod": null, "Headers": { "Host": "xxx.execute-api.eu-west-3.amazonaws.com", "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits", "Sec-WebSocket-Key": "xxx==", "Sec-WebSocket-Version": "13", "X-Amzn-Trace-Id": "Root=1-6034bfdc-53e605153673d3cb0d0c4177", "X-Forwarded-For": "185.153.165.122", "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https" }, "MultiValueHeaders": { "Host": [ "xxx.execute-api.eu-west-3.amazonaws.com" ], "Sec-WebSocket-Extensions": [ "permessage-deflate; client_max_window_bits" ], "Sec-WebSocket-Key": [ "xxx==" ], "Sec-WebSocket-Version": [ "13" ], "X-Amzn-Trace-Id": [ "Root=1-6034bfdc-53e605153673ddcb0d0cxxx" ], "X-Forwarded-For": [ "185.153.165.122" ], "X-Forwarded-Port": [ "443" ], "X-Forwarded-Proto": [ "https" ] }, "QueryStringParameters": null, "MultiValueQueryStringParameters": null, "PathParameters": null, "StageVariables": null, "RequestContext": { "Path": null, "AccountId": null, "ResourceId": null, "Stage": "Prod", "RequestId": "xxx=", "Identity": { "CognitoIdentityPoolId": null, "AccountId": null, "CognitoIdentityId": null, "Caller": null, "ApiKey": null, "ApiKeyId": null, "AccessKey": null, "SourceIp": "185.153.165.122", "CognitoAuthenticationType": null, "CognitoAuthenticationProvider": null, "UserArn": null, "UserAgent": null, "User": null, "ClientCert": null }, "ResourcePath": null, "HttpMethod": null, "ApiId": "xxx", "ExtendedRequestId": "xxx=", "ConnectionId": "xxx=", "ConnectionAt": 0, "DomainName": "xxx.execute-api.eu-west-3.amazonaws.com", "DomainPrefix": null, "EventType": "CONNECT", "MessageId": null, "RouteKey": "$connect", "Authorizer": null, "OperationName": null, "Error": null, "IntegrationLatency": null, "MessageDirection": "IN", "RequestTime": "23/Feb/2021:08:42:04 0000", "RequestTimeEpoch": 1614069724309, "Status": null }, "Body": null, "IsBase64Encoded": false }
and the APIGatewayProxyResponse is:
{ "statusCode": 200, "headers": null, "multiValueHeaders": null, "body": "Connected.", "isBase64Encoded": false }
- When
sendmessage
{"message":"sendmessage", "data":"hello world"}
the APIGatweayProxyRequest is:{ "Resource": null, "Path": null, "HttpMethod": null, "Headers": null, "MultiValueHeaders": null, "QueryStringParameters": null, "MultiValueQueryStringParameters": null, "PathParameters": null, "StageVariables": null, "RequestContext": { "Path": null, "AccountId": null, "ResourceId": null, "Stage": "Prod", "RequestId": "xxx=", "Identity": { "CognitoIdentityPoolId": null, "AccountId": null, "CognitoIdentityId": null, "Caller": null, "ApiKey": null, "ApiKeyId": null, "AccessKey": null, "SourceIp": "185.153.165.122", "CognitoAuthenticationType": null, "CognitoAuthenticationProvider": null, "UserArn": null, "UserAgent": null, "User": null, "ClientCert": null }, "ResourcePath": null, "HttpMethod": null, "ApiId": "xxx", "ExtendedRequestId": "xxx=", "ConnectionId": "xxx=", "ConnectionAt": 0, "DomainName": "4air0zk9w1.execute-api.eu-west-3.amazonaws.com", "DomainPrefix": null, "EventType": "MESSAGE", "MessageId": "xxx=", "RouteKey": "sendmessage", "Authorizer": null, "OperationName": null, "Error": null, "IntegrationLatency": null, "MessageDirection": "IN", "RequestTime": "23/Feb/2021:08:42:33 0000", "RequestTimeEpoch": 1614069753002, "Status": null }, "Body": "{"message":"sendmessage", "data":"hello world"}", "IsBase64Encoded": false }
и APIGatewayProxyResponse является:
{ "statusCode": 200, "headers": null, "multiValueHeaders": null, "body": "Data sent to 1 connection", "isBase64Encoded": false }
- Когда
$disconnect
запрос APIGateway выполняется:{ "Resource": null, "Path": null, "HttpMethod": null, "Headers": { "Host": "xxx.execute-api.eu-west-3.amazonaws.com", "x-api-key": "", "X-Forwarded-For": "", "x-restapi": "" }, "MultiValueHeaders": { "Host": [ "xxx.execute-api.eu-west-3.amazonaws.com" ], "x-api-key": [ "" ], "X-Forwarded-For": [ "" ], "x-restapi": [ "" ] }, "QueryStringParameters": null, "MultiValueQueryStringParameters": null, "PathParameters": null, "StageVariables": null, "RequestContext": { "Path": null, "AccountId": null, "ResourceId": null, "Stage": "Prod", "RequestId": "xxx=", "Identity": { "CognitoIdentityPoolId": null, "AccountId": null, "CognitoIdentityId": null, "Caller": null, "ApiKey": null, "ApiKeyId": null, "AccessKey": null, "SourceIp": "185.153.165.122", "CognitoAuthenticationType": null, "CognitoAuthenticationProvider": null, "UserArn": null, "UserAgent": null, "User": null, "ClientCert": null }, "ResourcePath": null, "HttpMethod": null, "ApiId": "xxx", "ExtendedRequestId": "xxxA=", "ConnectionId": "xxx=", "ConnectionAt": 0, "DomainName": "xxx.execute-api.eu-west-3.amazonaws.com", "DomainPrefix": null, "EventType": "DISCONNECT", "MessageId": null, "RouteKey": "$disconnect", "Authorizer": null, "OperationName": null, "Error": null, "IntegrationLatency": null, "MessageDirection": "IN", "RequestTime": "23/Feb/2021:08:42:41 0000", "RequestTimeEpoch": 1614069761070, "Status": null }, "Body": null, "IsBase64Encoded": false }
и APIGatewayProxyResponse является:
{ "statusCode": 200, "headers": null, "multiValueHeaders": null, "body": "Disconnected.", "isBase64Encoded": false }
Комментарии:
1. Привет. Это хороший и интересный вопрос. К сожалению, я не знаю ответа, и, как вы упомянули, моя компания сопоставляет отдельные лямбды непосредственно с конечными точками. Хотя мне любопытно, что произойдет, если вы добавите пакет SignalR и сопоставите свои концентраторы с конечными точками, как мы делаем это в стандарте asp.net основное приложение? Вот так:
endpoints.MapControllers(); endpoints.MapHub<YourHub>("/signalr/yourHub");
?2. Из того, что я прочитал здесь github.com/dotnet/aspnetcore/issues/9522 это не что-то легкое или даже возможное. В конце шлюз api обрабатывает соединения, и лямбда «просто» должна реагировать на подключение, отключение и другие события