Desenvolvimento de funções do Azure e a configuração com o serviço do Azure SignalR

Os aplicativos do Azure Functions podem usar as associações do Azure SignalR Service para adicionar recursos em tempo real. Os aplicativos cliente usam SDKs de cliente disponíveis em vários idiomas para se conectar ao Azure SignalR Service e receber mensagens em tempo real.

Este artigo descreve os conceitos para desenvolver e configurar um aplicativo Azure Function integrado ao SignalR Service.

Configuração do serviço SignalR

O Serviço do Azure SignalR pode ser configurado de diferentes modos. Quando usado com o Azure Functions, o serviço deve ser configurado no modo Sem servidor.

No portal do Azure, localize a página Configurações do recurso SignalR Service. Defina o modo de serviço como Sem servidor.

Modo de Serviço do SignalR

Desenvolvimento do Azure Functions

Um aplicativo em tempo real sem servidor criado com o Azure Functions e o serviço do Azure SignalR requer pelo menos dois Azure Functions:

  • Uma função negotiate que o cliente chama para obter um token de acesso válido do Serviço do SignalR e uma URL do ponto de extremidade.
  • Uma ou mais funções que lidam com mensagens enviadas do Serviço do SignalR para clientes.

Função de negociação

Um aplicativo cliente requer um token de acesso válido para se conectar ao Azure SignalR Service. Um token de acesso pode ser anônimo ou autenticado para uma ID de usuário. Os aplicativos do serviço SignalR sem servidor exigem um ponto de extremidade HTTP denominado negotiate para obter um token e outras informações de conexão, como a URL do ponto de extremidade do serviço SignalR.

Use uma Função do Azure disparada por HTTP e a associação de entrada SignalRConnectionInfo para gerar o objeto de informações de conexão. A função deve ter uma rota HTTP que termina em /negotiate.

Com o modelo baseado em classe em C#, você não precisa da associação de entrada SignalRConnectionInfo e pode adicionar declarações personalizadas com muito mais facilidade. Para obter mais informações, confira Experiência de negociação no modelo com classe base.

Para obter mais informações sobre a função negotiate, consulte Desenvolvimento do Azure Functions.

Para saber mais sobre como criar um token autenticado, consulte Usando a Autenticação do Serviço de Aplicativo.

Lidar com mensagens enviadas do serviço SignalR

Use a ligação SignalRTrigger para lidar com mensagens enviadas do serviço SignalR. Você pode ser notificado quando os clientes enviam mensagens ou se conectam ou desconectam.

Para obter mais informações, consulte a referência de associação do gatilho de Serviço do SignalR.

Você também precisa configurar seu endpoint de função como um endpoint upstream para que o serviço acione a função quando houver mensagem de um cliente. Para mais informações sobre como configurar endpoints upstream, confira Endpoints upstream.

Observação

O Serviço do SignalR não dá suporte à mensagem StreamInvocation de um cliente no modo sem servidor.

Envio de mensagens e gerenciamento de membros do grupo

Use a associação de saída SignalR para enviar mensagens para clientes conectados ao Serviço SignalR do Azure. Você pode transmitir mensagens a todos os clientes ou enviá-las ao subconjunto de clientes. Por exemplo, envie apenas mensagens para clientes autenticados com uma ID de usuário específica ou apenas para um grupo específico.

Os usuários podem ser adicionados a um ou mais grupos. Você também pode usar a ligação de saída SignalR para adicionar ou remover usuários de / para grupos.

Para obter mais informações, consulte a SignalR SignalR.

Hubs SignalR

SignalR tem um conceito de hubs. Cada conexão de cliente e cada mensagem enviada do Azure Functions tem como escopo um hub específico. Você pode usar hubs como uma forma de separar suas conexões e mensagens em namespaces lógicos.

Modelo baseado em classe

O modelo baseado em classe é dedicado a C#.

O modelo com classe base fornece uma melhor experiência de programação, que pode substituir as associações de entrada e saída do SignalR pelos seguintes recursos:

  • Negociação mais flexível, envio de mensagens e gerenciamento da experiência de grupos.
  • Há suporte para mais funcionalidades de gerenciamento, inclusive conexões de fechamento, verificando se existe uma conexão, um usuário ou um grupo.
  • Hub fortemente tipado
  • Nome do hub unificado e configuração da cadeia de conexão em um só lugar.

O código a seguir demonstra como gravar as associações do SignalR no modelo com classe base:

Em primeiro lugar, defina o hub derivado de uma classe ServerlessHub:

[SignalRConnection("AzureSignalRConnectionString")]
public class Functions : ServerlessHub
{
    private const string HubName = nameof(Functions); // Used by SignalR trigger only

    public Functions(IServiceProvider serviceProvider) : base(serviceProvider)
    {
    }

    [Function("negotiate")]
    public async Task<HttpResponseData> Negotiate([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
    {
        var negotiateResponse = await NegotiateAsync(new() { UserId = req.Headers.GetValues("userId").FirstOrDefault() });
        var response = req.CreateResponse();
        response.WriteBytes(negotiateResponse.ToArray());
        return response;
    }

    [Function("Broadcast")]
    public Task Broadcast(
    [SignalRTrigger(HubName, "messages", "broadcast", "message")] SignalRInvocationContext invocationContext, string message)
    {
        return Clients.All.SendAsync("newMessage", new NewMessage(invocationContext, message));
    }

    [Function("JoinGroup")]
    public Task JoinGroup([SignalRTrigger(HubName, "messages", "JoinGroup", "connectionId", "groupName")] SignalRInvocationContext invocationContext, string connectionId, string groupName)
    {
        return Groups.AddToGroupAsync(connectionId, groupName);
    }
}

No arquivo Program.cs, registre o hub sem servidor:

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(b => b.Services
        .AddServerlessHub<Functions>())
    .Build();

Experiência de negociação no modelo com classe base

Em vez de usar a associação de entrada SignalR [SignalRConnectionInfoInput], a negociação no modelo baseado em classe pode ser mais flexível. A classe base ServerlessHub tem um método NegotiateAsync, que permite aos usuários personalizar opções de negociação, como userId, claims etc.

Task<BinaryData> NegotiateAsync(NegotiationOptions? options = null)

Envio de mensagens e gerenciamento da experiência no modelo com classe base

Você pode enviar mensagens, gerenciar grupos ou gerenciar clientes acessando os membros fornecidos pela classe base ServerlessHub.

  • ServerlessHub.Clients para enviar mensagens aos clientes.
  • ServerlessHub.Groups para gerenciar conexões com grupos, como adicionar conexões a grupos, remover conexões de grupos.
  • ServerlessHub.UserGroups para gerenciar usuários com grupos, como adicionar usuários a grupos, remover usuários de grupos.
  • ServerlessHub.ClientManager para verificar a existência de conexões, fechar conexões etc.

Hub fortemente tipado

O hub fortemente tipado permite que você use métodos fortemente tipados ao enviar mensagens aos clientes. Para usar um hub fortemente tipado no modelo com classe base, extraia os métodos de cliente em uma interface T e faça com que a classe do hub seja derivada de ServerlessHub<T>.

O código a seguir é um exemplo de interface para métodos de cliente.

public interface IChatClient
{
    Task newMessage(NewMessage message);
}

Em seguida, você pode usar os métodos fortemente tipados da seguinte maneira:

[SignalRConnection("AzureSignalRConnectionString")]
public class Functions : ServerlessHub<IChatClient>
{
    private const string HubName = nameof(Functions);  // Used by SignalR trigger only

    public Functions(IServiceProvider serviceProvider) : base(serviceProvider)
    {
    }

    [Function("Broadcast")]
    public Task Broadcast(
    [SignalRTrigger(HubName, "messages", "broadcast", "message")] SignalRInvocationContext invocationContext, string message)
    {
        return Clients.All.newMessage(new NewMessage(invocationContext, message));
    }
}

Observação

Você pode obter um exemplo de projeto completo do GitHub.

Nome do hub unificado e configuração da cadeia de conexão em um só lugar

  • O nome da classe do hub sem servidor é usado automaticamente como HubName.
  • Você deve ter notado o atributo SignalRConnection usado nas classes do hub sem servidor da seguinte maneira:
    [SignalRConnection("AzureSignalRConnectionString")]
    public class Functions : ServerlessHub<IChatClient>
    
    Ele permite que você personalize o local em que está a cadeia de conexão do hub sem servidor. Se estiver ausente, o valor padrão AzureSignalRConnectionString será usado.

Importante

Os gatilhos do SignalR e os hubs sem servidor são independentes. Portanto, o nome da classe do hub sem servidor e atributo SignalRConnection não alteram as configurações dos gatilhos do SignalR, mesmo que você use os gatilhos do SignalR dentro do hub sem servidor.

Desenvolvimento de cliente

Os aplicativos de cliente SignalR podem usar o SDK do cliente SignalR em um dos vários idiomas para conectar-se facilmente e receber mensagens do Serviço SignalR do Azure.

Configurando uma conexão de cliente

Para se conectar ao SignalR Service, um cliente deve concluir uma negociação de conexão bem-sucedida que consiste nestas etapas:

  1. Faça uma solicitação para negotiate o endpoint HTTP discutido acima para obter informações de conexão válidas
  2. Conecte-se ao serviço SignalR usando a URL do terminal de serviço e o token de acesso obtido do ponto de extremidade negotiate.

Os SDKs do cliente SignalR já contêm a lógica necessária para realizar o handshake de negociação. Passe a URL do endpoint de negociação, sem o segmento negotiate, para o HubConnectionBuilder do SDK. Aqui está um exemplo em JavaScript:

const connection = new signalR.HubConnectionBuilder()
  .withUrl("https://my-signalr-function-app.azurewebsites.net/api")
  .build();

Por convenção, o SDK anexa /negotiate automaticamente ao URL e o usa para iniciar a negociação.

Observação

Se você estiver usando o JavaScript /TypeScript SDK em um navegador, será necessário habilitar o compartilhamento de recursos de origem cruzada (CORS) em seu aplicativo de funções.

Para obter mais informações sobre como usar o SDK do cliente SignalR, consulte a documentação do seu idioma:

Enviando mensagens de um cliente para o serviço

Se você configurou o upstream para o recurso SignalR, pode enviar mensagens de um cliente para o Azure Functions usando qualquer cliente SignalR. Aqui está um exemplo em JavaScript:

connection.send("method1", "arg1", "arg2");

Configuração do Azure Functions

Os aplicativos do Azure Function que se integram ao Azure SignalR Service podem ser implantados como qualquer aplicativo típico do Azure Function, usando técnicas como implantação contínua, implantação zip e execução do pacote.

No entanto, há algumas considerações especiais para aplicativos que usam as ligações SignalR Service. Se o cliente for executado em um navegador, o CORS deve ser ativado. E se o aplicativo exigir autenticação, você pode integrar o ponto de extremidade de negociação com a Autenticação do Serviço de Aplicativo.

Habilitando CORS

O cliente JavaScript/TypeScript faz solicitações HTTP para a função de negociação para iniciar a negociação da conexão. Quando o aplicativo cliente está hospedado em um domínio diferente do aplicativo Azure Function, o compartilhamento de recursos de origem cruzada (CORS) deve ser habilitado no aplicativo de funções ou o navegador bloqueará as solicitações.

Localhost

Ao executar o aplicativo Function em seu computador local, você pode adicionar uma seção Host a local.settings.json para habilitar o CORS. Na seção Host, adicione duas propriedades:

  • CORS - insira o URL base que é a origem do aplicativo cliente
  • CORSCredentials - defina-o para true para permitir solicitações "withCredentials

Exemplo:

{
  "IsEncrypted": false,
  "Values": {
    // values
  },
  "Host": {
    "CORS": "http://localhost:8080",
    "CORSCredentials": true
  }
}

Cloud - Azure Functions CORS

Para habilitar o CORS em um aplicativo Function do Azure, vá para a tela de configuração do CORS na guia Recursos da plataforma de seu aplicativo de Função no portal do Azure.

Observação

A configuração do CORS ainda não está disponível no plano de consumo do Azure Functions em Linux. Use o Gerenciamento de API do Azure para habilitar o CORS.

O CORS com Access-Control-Allow-Credentials deve ser habilitado para o cliente SignalR chamar a função de negociação. Para habilitá-lo, marque a caixa de seleção.

Na seção origens permitidas, adicione uma entrada com a URL base de origem do seu aplicativo Web.

Configurando o CORS

Cloud - Gerenciamento de API do Azure

O Gerenciamento de API do Azure fornece um gateway de API que adiciona recursos aos serviços de back-end existentes. Você pode usá-lo para adicionar CORS ao seu aplicativo de funções. Ele oferece um nível de consumo com preços de pagamento por ação e uma concessão gratuita mensal.

Consulte a documentação de Gerenciamento de API para obter informações sobre como importar um aplicativo Azure Function. Depois de importado, você pode adicionar uma política de entrada para habilitar CORS com suporte Access-Control-Allow-Credentials.

<cors allow-credentials="true">
  <allowed-origins>
    <origin>https://azure-samples.github.io</origin>
  </allowed-origins>
  <allowed-methods>
    <method>GET</method>
    <method>POST</method>
  </allowed-methods>
  <allowed-headers>
    <header>*</header>
  </allowed-headers>
  <expose-headers>
    <header>*</header>
  </expose-headers>
</cors>

Configure seus clientes SignalR para usar a URL de Gerenciamento de API.

Usando a autenticação do Serviço de Aplicativo

O Azure Functions tem autenticação interna, oferecendo suporte a provedores populares, como Facebook, Twitter, Conta da Microsoft, Google e Microsoft Entra ID. Esse recurso pode ser integrado à associação SignalRConnectionInfo para criar conexões com o Azure SignalR Service que são autenticadas para uma ID de usuário. Seu aplicativo pode enviar mensagens usando a ligação de saída SignalR direcionada a esse ID de usuário.

No portal do Azure, na guia de Recursos da plataforma do seu aplicativo de função, abra a janela Configurações de autenticação/autorização. Siga a documentação para Autenticação de Serviço de Aplicativo para configurar a autenticação usando um provedor de identidade de sua escolha.

Depois de configuradas, as solicitações HTTP autenticadas incluem os cabeçalhos x-ms-client-principal-name e x-ms-client-principal-id que contêm o nome de usuário e a ID do usuário da identidade autenticada, respectivamente.

Você pode usar esses cabeçalhos em sua configuração de ligação SignalRConnectionInfo para criar conexões autenticadas. Este é um exemplo de função de negociação do C# que usa o cabeçalho x-ms-client-principal-id.

[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
    [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
    [SignalRConnectionInfo
        (HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")]
        SignalRConnectionInfo connectionInfo)
{
    // connectionInfo contains an access key token with a name identifier claim set to the authenticated user
    return connectionInfo;
}

Em seguida, você pode enviar mensagens para esse usuário, definindo a propriedade UserId de uma mensagem SignalR.

[FunctionName("SendMessage")]
public static Task SendMessage(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
    [SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
    return signalRMessages.AddAsync(
        new SignalRMessage
        {
            // the message will only be sent to these user IDs
            UserId = "userId1",
            Target = "newMessage",
            Arguments = new [] { message }
        });
}

Para obter informações sobre outras linguagens, consulte as associações do Azure SignalR Service para Azure Functions.

Próximas etapas

Neste artigo, você aprendeu como desenvolver e configurar aplicativos de serviço SignalR sem servidor usando o Azure Functions. Tente criar um aplicativo você mesmo usando um dos inícios rápidos ou tutoriais na página Visão geral do serviço SignalR.