Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SignalR and WebSocket API in Amazon API Gateway #9522

Closed
genifycom opened this issue Apr 18, 2019 · 8 comments
Closed

SignalR and WebSocket API in Amazon API Gateway #9522

genifycom opened this issue Apr 18, 2019 · 8 comments
Labels
area-signalr Includes: SignalR clients and servers
Milestone

Comments

@genifycom
Copy link

Is your feature request related to a problem? Please describe.

In WebSocket API in Amazon API Gateway, the Gateway handles the websocket $connect, $disconnect and routing. This is a similar idea to the SignalR service and allows load spreading.

Describe the solution you'd like

I would like to be able to use SignalR with the WebSocket API in Amazon API Gateway using ASP.Net Web application running in AWS Lambda

Describe alternatives you've considered

We can use SignalR via HTTP in a single AWS Lambda function but it will not load balance. Because the $connect, $disconnect and message are handled by the Websocket API Gateway, we need a way to be able to route these calls to the appropriate hub for handling.

Additional context

DotNet Core on AWS Lambda is a major piece for us and others. Handling SignalR just on Azure using the SignalR Service is counter to the culture of openness and SignalR on AWS Lambda should get equal consideration. Please offer us a way to use SignalR in the AWS environment.

@Eilon Eilon added the area-signalr Includes: SignalR clients and servers label Apr 19, 2019
@davidfowl
Copy link
Member

How would you like to use SignalR in the amazon API gateway? From a cursory glance it looks as though the websocket gateway is a very specific protocol plus a very specific way to route messages. I haven't looked deeply enough but I don't see any obvious integration, at least not without significantly changing either side of the equation (how signalr works or how the websocket gate way works).

cc @normj for any ideas

@genifycom
Copy link
Author

Hi David,

Thank you for the response. Yes I have already raised this with Norm.
I have also looked at this package https://github.com/eByte23/Amazon.Lambda.SignalR

So how does the SignalR Service communicate with a separate ASP.Net app? Does it open a local WebSocket between the service and the app, so the app is unaware who it is talking to?

Yes I understand that in the WebSocket API in Amazon API Gateway, the incoming WebSocket request is turned into a JSON event with a connection Id. So the receiving app just receives an event.

What I was hoping was that I could internally route this incoming and outgoing API connection Id to SignalR so I could use the framework. From your last SignalR presentation, it looked like the layers were more conducive to different protocols,package types and communication alternatives.

Regards,

Dave

@davidfowl
Copy link
Member

So how does the SignalR Service communicate with a separate ASP.Net app? Does it open a local WebSocket between the service and the app, so the app is unaware who it is talking to?

Yes, the SignalR service acts like a websocket proxy. All of the protocol parsing is handled in the framework itself, the service just handles the websocket traffic and fowards those bytes to the application over a different transport (see https://github.com/Azure/azure-signalr/blob/dev/docs/internal.md for more details). As far as SignalR is concerned, it's running on top of a different transport; instead of bytes coming from a client, it's coming from the a multiplexed websocket connection.

Yes I understand that in the WebSocket API in Amazon API Gateway, the incoming WebSocket request is turned into a JSON event with a connection Id. So the receiving app just receives an event.

Right, it's JSON in a specific format so that it can be routed. Essentially, thats required data as part of using the amazon service to even function it seems.

What I was hoping was that I could internally route this incoming and outgoing API connection Id to SignalR so I could use the framework. From your last SignalR presentation, it looked like the layers were more conducive to different protocols,package types and communication alternatives.

Maybe but I'm not connecting the dots, SignalR is flexible and has 2 built in protocols (even some 3rd party ones) and you can plug in your own but its possible you'd need to replace protocols on both sides to have a chance at this working. Is there a way to forward the websocket traffic from the client to a backend application?

@davidfowl
Copy link
Member

So looking at https://github.com/eByte23/Amazon.Lambda.SignalR it seems like it might be possible:

It seems like the interaction with the backend is over http? I could be reading it wrong but it looks like you need to handle incoming HTTP request and basically build a transport on top of that. Then to response, possibly you're given a callback URL to send data to when you want to respond. This seems extremely inefficient (especially if you can only respond a connection at a time) but maybe it works fine for smaller workloads (or maybe there are capabilities I'm missing)

@classifieds-dev
Copy link

classifieds-dev commented Apr 11, 2020

I originally built my project using MassTransit which provides a backplane for broadcasting events distributed across multiples servers/nodes.

https://masstransit-project.com/advanced/signalr/

I began migrating to api gateway using the web socket integration with little luck. From what I'm reading in this thread it seems to me a better option would be to use the standard http api gateway alongside mass transit for the backplane.

Before I migrated to aws api gateway I was also using Ocelot which is a .NET core API gateway since my plan was to run containers at that time not serverless. Ocelot has a load balancer for signal r build right into it.

Instead of using api gateway could I use a load balancer lambda integration. Would that function properly across multiples nodes?

@classifieds-dev
Copy link

classifieds-dev commented Apr 20, 2020

I think for the time being it is much easier to just abandon SignalR when using the AWS API Gateway. I've come up with this boilerplate code that connects, and disconnects using the api gateway. Its based on the work done here: https://github.com/normj/netcore-simple-websockets-chat-app and inside the aws aspnetcore server package itself.

using Microsoft.AspNetCore.Hosting;
using Amazon.Lambda.AspNetCoreServer;
using Microsoft.AspNetCore.Http.Features;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using Newtonsoft.Json;
using Serilog;
using System.Threading.Tasks;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Reflection;
using System;

// 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 Chat
{
    public class Handler
    {

        public APIGatewayProxyResponse FunctionHandlerAsync(APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext)
        {
            Start();

            lambdaContext.Logger.LogLine("CONTEXT: " + JsonConvert.SerializeObject(lambdaContext));
            lambdaContext.Logger.LogLine("REQUEST: " + JsonConvert.SerializeObject(apiGatewayRequest));

            if(apiGatewayRequest.RequestContext.EventType == "CONNECT")
            {
                lambdaContext.Logger.LogLine($"We have been connected to the websocket! Connection Id = {apiGatewayRequest.RequestContext.ConnectionId}");
            } else if(apiGatewayRequest.RequestContext.EventType == "DISCONNECT")
            {
                lambdaContext.Logger.LogLine($"We have been disconnected from the websocket! Connection Id = {apiGatewayRequest.RequestContext.ConnectionId}");
            } else
            {
                lambdaContext.Logger.LogLine($"unknown event in web socket. Connection Id = {apiGatewayRequest.RequestContext.ConnectionId}");
            }

            return new APIGatewayProxyResponse
            {
                StatusCode = 200,
                Body = "Connected."
            };
        }

        protected void Start()
        {
            var builder = CreateWebHostBuilder();
            Init(builder);
        }

        protected void Init(IWebHostBuilder builder)
        {
            builder
                .UseSerilog((context, loggerConfiguration) =>
                {
                    loggerConfiguration.ReadFrom.Configuration(context.Configuration);
                }, true, false);
                //.UseStartup<Startup>();
        }

        protected virtual IWebHostBuilder CreateWebHostBuilder()
        {
            var builder = new WebHostBuilder()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    var env = hostingContext.HostingEnvironment;

                    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                          .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

                    if (env.IsDevelopment())
                    {
                        var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
                        if (appAssembly != null)
                        {
                            config.AddUserSecrets(appAssembly, optional: true);
                        }
                    }

                    config.AddEnvironmentVariables();
                })
                .ConfigureLogging((hostingContext, logging) =>
                {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));

                    if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LAMBDA_TASK_ROOT")))
                    {
                        logging.AddConsole();
                        logging.AddDebug();
                    }
                    else
                    {
                        logging.AddLambdaLogger(hostingContext.Configuration, "Logging");
                    }
                })
                .UseDefaultServiceProvider((hostingContext, options) =>
                {
                    options.ValidateScopes = hostingContext.HostingEnvironment.IsDevelopment();
                });

            return builder;
        }

    }
}

@davidfowl
Copy link
Member

Sounds good!

@ghost
Copy link

ghost commented Nov 12, 2020

Thank you for contacting us. Due to a lack of activity on this discussion issue we're closing it in an effort to keep our backlog clean. If you believe there is a concern related to the ASP.NET Core framework, which hasn't been addressed yet, please file a new issue.

This issue will be locked after 30 more days of inactivity. If you still wish to discuss this subject after then, please create a new issue!

@ghost ghost closed this as completed Nov 12, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 12, 2020
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-signalr Includes: SignalR clients and servers
Projects
None yet
Development

No branches or pull requests

4 participants