Azure / azure-functions-signalrservice-extension

Azure Functions bindings for SignalR Service. Project moved to https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/signalr/Microsoft.Azure.WebJobs.Extensions.SignalRService .
MIT License
97 stars 48 forks source link

How to use Redis as backplane? #104

Closed domenichelfenstein closed 4 years ago

domenichelfenstein commented 4 years ago

I've tried to add SignalR with Redis as backplane as I would have when using a normal Azure App Service:

using Microsoft.Azure.Functions.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(Startup))]

namespace SampleFunctions
{
    using Microsoft.Extensions.DependencyInjection;

    public class Startup : FunctionsStartup
    {
        public override void Configure(
            IFunctionsHostBuilder builder)
        {
            builder.Services
                .AddSignalR()
                .AddRedis("<connectionString>");
        }
    }
}

Then I set up the simplest HttpTrigger I could think of:

public class PingFunctions
{
    [FunctionName("Ping")]
    public Task<IActionResult> CallPing(
        [HttpTrigger(
            AuthorizationLevel.Anonymous,
            "get",
            Route = null)] HttpRequest req)
    {
        return Task.FromResult<IActionResult>(new OkResult());
    }
}

Unfortunately when I've tried calling then ping function I got a status code 401 response.

The thing is: We already have a Redis backplane and using it in other services. Now I wanted to be able to add notifications to the backplane, so that the other services can distribute the SignalR-Notifications to the clients.

How can I do that?

domenichelfenstein commented 4 years ago

Still no response...

Is it simply not possible, or is this just the wrong place for asking this question? If the latter is the case, what would be the right place?

JialinXin commented 4 years ago

Sorry I couldn't understand your request and would you explain a bit more?

Do you want to use SignalR between client and service or clients, what's Redis used in the function?

  1. client -> SignalR -> services?
  2. client-> SignalR -> client?

Besides, SignalR requires to negotiation before send messages. And simply AddSignalR() without UseSignalR() or UseEndpoints()(after AspNetCore3.0) won't real work with SignalR. Not able to saw your complete code and couldn't know the blocking part. You may take a look at our sample for some reference first: https://github.com/Azure/azure-functions-signalrservice-extension/blob/dev/samples/simple-chat/csharp/FunctionApp/Functions.cs.

domenichelfenstein commented 4 years ago

Hi @JialinXin Thank you for your response.

Our scenario: Until now, we had 2 App Services and an Angular Client. The client only speaks with Service A, which also contains a SignalR endpoint. Service B exists only for background work (long-running calculations and/or processing ServiceBus messages). After the completion of such messages, the background-working service sometimes wants to inform the client about the new state of the system. Right now, it does that via the Redis backplane (both services A and B are attached to the same Redis backplane for SignalR, but only service A provides a SignalR endpoint to the client). This all works for now. But we intended to refactor service B (the background-worker) to Azure Functions for scalability and cost reasons.

I took a look at your sample and what I want to have in the end is a function like this (I just copied the sample):

[FunctionName("broadcast")]
public static async Task Broadcast(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequest req,
    [SignalR(HubName = "simplechat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
    var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
    var serviceHubContext = await StaticServiceHubContextStore.Get().GetAsync("simplechat");
    await serviceHubContext.Clients.All.SendAsync("newMessage", message);
}

The question is, how can I make this sample work with Redis instead of Azure SignalR Service as the backplane. (https://docs.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-3.1#redis-backplane) (https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane?view=aspnetcore-2.1)

Thanks in advance for your help

davidfowl commented 4 years ago

You don’t need to when using the service. The service itself is the backplane in this case

domenichelfenstein commented 4 years ago

You don’t need to when using the service. The service itself is the backplane in this case

Yes, I know that. But what if I want to use Redis instead of the Azure SignalR Service?

It seems like the whole Redis as backplane idea has been declared obsolete by MS. Is this true?

JialinXin commented 4 years ago

So you'd like to make ServiceB publish the message to both Azure SignalR Service and Redis in the function to ensure it could be received by ServiceA? We don't support build-in Redis backplane like that in azure function now, though inside our service we have some strategy to ensure message transfer. Probably you'd just do the backplane publish inside the function like this:

private static IConnectionMultiplexer _redisConnectionMultiplexer =
        ConnectionMultiplexer.Connect("<redis-connecion-string>");
[FunctionName("broadcast")]
public static async Task Broadcast(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequest req,
    [SignalR(HubName = "simplechat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
    var message = new JsonSerializer().Deserialize<ChatMessage>(new JsonTextReader(new StreamReader(req.Body)));
    var serviceHubContext = await StaticServiceHubContextStore.Get().GetAsync("simplechat");
    await serviceHubContext.Clients.All.SendAsync("newMessage", message);

    // with some check to determine send redis.
    var db = _redisConnectionMultiplexer.GetDatabase();
    db.StringSet("somekey", message);
}
domenichelfenstein commented 4 years ago

So you'd like to make ServiceB publish the message to both Azure SignalR Service and Redis in the function to ensure it could be received by ServiceA?

I'm really starting to repeat myself: I want to use Redis as backplane instead of the Azure SignalR Service. I want to use SignalR without using the Azure SignalR Service.

I understand if you say this is not supported by your libraries (it was supported in ASP.NET core, but it maybe not with Azure Functions).

I'm really sorry if I can't express myself any better. English isn't my native language, as you might already have guessed...

davidfowl commented 4 years ago

I see, you’re trying to send to redis from an azure function. It might be possible today by using the original code you have if you inject the IHubContext\<YourHub> into your function class.

It doesn’t really have much to do with the severless version of azure signalr though.

domenichelfenstein commented 4 years ago

Yes, that's what I'm trying to achieve.

Also, the injection of IHubContext<MyHub> works for functions with ServiceBusTriggers. I can confirm that.*

However, as I said in my initial post, when I use my original code, all the HttpTrigger functions don't work any longer because they suddenly start to respond with 401. This is because of the AddSignalR() method, which seems to start a process that creates an Authorization-Check whenever a HttpTrigger function is called.

* It would probably also work for all other functions with other kinds of triggers.

domenichelfenstein commented 4 years ago

It's true; my problem has probably not much to do with the serverless version of azure signalr. It's more a problem of integrating plain old signalr with azure functions.

The question remains: How could I solve this, and if this is the wrong place for asking, what would be the right one?

davidfowl commented 4 years ago

which seems to start a process that creates an Authorization-Check whenever a HttpTrigger function is called.

I don't see how AddSignalR would cause a 401 in the HttpTrigger.

The question remains: How could I solve this, and if this is the wrong place for asking, what would be the right one?

It's fine to ask it here but it wasn't initially clear what you were doing since it was in the context of the Azure SignalR Service (which you aren't using).

cc @fabiocav for ideas on the 401.

davidfowl commented 4 years ago

@domenichelfenstein if you remove the call to AddSignalR, does your ping function work?

domenichelfenstein commented 4 years ago

@domenichelfenstein if you remove the call to AddSignalR, does your ping function work?

Yes it does. So far I was able to track the problem down to the service.AddConnections(); call which happens within AddSignalR(). But then I couldn't dig any deeper because I don't have the sources of AddConnections() and don't know where to find them...

davidfowl commented 4 years ago

Interesting, try calling AddSignalRCore instead of AddSignalR.

The source for AddConnections is here and it's likely this call to AddAuthorization https://github.com/dotnet/aspnetcore/blob/7669cf0d036cc0cb3b481aba49253cd35d85c633/src/SignalR/common/Http.Connections/src/ConnectionsDependencyInjectionExtensions.cs#L25

domenichelfenstein commented 4 years ago

Yes, that's actually more than likely :)

I'm gonna give it a try. Thank you so much, you've already helped me a lot!

domenichelfenstein commented 4 years ago

try calling AddSignalRCore instead of AddSignalR.

It still doesn't work, which isn't a huge surprise since it contains this line: https://github.com/dotnet/aspnetcore/blob/c84e37f30def9ff0b2d12e877e3de6c283be6145/src/SignalR/server/Core/src/SignalRDependencyInjectionExtensions.cs#L32

I'm asking myself if I should just copy the whole body of this method and remove the AddAuthorization() line...

domenichelfenstein commented 4 years ago

...which of course doesn't work, because many of the classes I would need are internal :(

davidfowl commented 4 years ago

Yes, the implementations are internal. Lets move this to https://github.com/azure/azure-functions-host/issues

domenichelfenstein commented 4 years ago

Sure! Shall I create a new issue over there?