Tutorial: criar um aplicativo de chat com o serviço Azure Web PubSub
No Tutorial de mensagem de publicação e assinatura, você aprenderá os conceitos básicos de publicação e assinatura de mensagens com o Azure Web PubSub. Neste tutorial, você vai aprender sobre o sistema de eventos do Azure Web PubSub e usá-lo para criar um aplicativo web completo com funcionalidade de comunicação em tempo real.
Neste tutorial, você aprenderá a:
- Criar uma instância do serviço Web PubSub
- Configurar as definições do manipulador de eventos para o Azure Web PubSub
- Lidar com eventos no servidor do aplicativo e criar um aplicativo de chat em tempo real
Caso você não tenha uma assinatura do Azure, crie uma conta gratuita do Azure antes de começar.
Pré-requisitos
Use o ambiente Bash no Azure Cloud Shell. Para obter mais informações, confira Início Rápido para Bash no Azure Cloud Shell.
Se preferir executar os comandos de referência da CLI localmente, instale a CLI do Azure. Para execuções no Windows ou no macOS, considere executar a CLI do Azure em um contêiner do Docker. Para obter mais informações, confira Como executar a CLI do Azure em um contêiner do Docker.
Se estiver usando uma instalação local, entre com a CLI do Azure usando o comando az login. Para concluir o processo de autenticação, siga as etapas exibidas no terminal. Para ver outras opções de entrada, confira Conectar-se com a CLI do Azure.
Quando solicitado, instale a extensão da CLI do Azure no primeiro uso. Para obter mais informações sobre extensões, confira Usar extensões com a CLI do Azure.
Execute az version para localizar a versão e as bibliotecas dependentes que estão instaladas. Para fazer a atualização para a versão mais recente, execute az upgrade.
- Essa configuração requer a versão 2.22.0 ou superior da CLI do Azure. Se você está usando o Azure Cloud Shell, a versão mais recente já está instalada.
Criar uma instância do Azure Web PubSub
Criar um grupo de recursos
Um grupo de recursos é um contêiner lógico no qual os recursos do Azure são implantados e gerenciados. Use o comando az group create para criar um grupo de recursos chamado myResourceGroup
no local eastus
.
az group create --name myResourceGroup --location EastUS
Criar uma instância do Web PubSub
Execute az extension add para instalar ou atualizar a extensão webpubsub para a versão atual.
az extension add --upgrade --name webpubsub
Use o comando az webpubsub create da CLI do Azure para criar um Web PubSub no grupo de recursos criado. O seguinte comando cria um recurso gratuito do Web PubSub no grupo de recursos myResourceGroup no EastUS:
Importante
Cada recurso Web PubSub precisa ter um nome exclusivo. Substitua <nome-de recurso-exclusivo> pelo nome do Web PubSub nos exemplos a seguir.
az webpubsub create --name "<your-unique-resource-name>" --resource-group "myResourceGroup" --location "EastUS" --sku Free_F1
A saída deste comando mostra as propriedades do recurso recém-criado. Anote as duas propriedades listadas abaixo:
- Nome do Recurso: o nome que você forneceu ao parâmetro
--name
acima. - hostName: no exemplo, o nome do host é
<your-unique-resource-name>.webpubsub.azure.com/
.
Nesse ponto, sua conta do Azure é a única autorizada a executar qualquer operação nesse novo recurso.
Obter o ConnectionString para uso posterior
Importante
Uma cadeia de conexão inclui as informações de autorização necessárias para que o seu aplicativo acesse o serviço Azure Web PubSub. A chave de acesso dentro da cadeia de conexão é semelhante a uma senha raiz para o serviço. Em ambientes de produção, sempre tenha o cuidado de proteger as chaves de acesso. Utilize o Azure Key Vault para gerenciar e girar suas chaves com segurança. Evite distribuir chaves de acesso para outros usuários, fazer hard-coding com elas ou salvá-las em qualquer lugar em texto sem formatação que seja acessível a outras pessoas. Gire suas chaves se você acredita que elas podem ter sido comprometidas.
Use o comando az webpubsub key da CLI do Azure para obter a ConnectionString do serviço. Substitua o espaço reservado <your-unique-resource-name>
pelo nome da instância do Azure Web PubSub.
az webpubsub key show --resource-group myResourceGroup --name <your-unique-resource-name> --query primaryConnectionString --output tsv
Copie a cadeia de conexão para usar mais tarde.
Copie o ConnectionString buscado e defina-o na variável de ambiente WebPubSubConnectionString
, que o tutorial lerá posteriormente. Substitua <connection-string>
abaixo pelo ConnectionString buscado.
export WebPubSubConnectionString="<connection-string>"
SET WebPubSubConnectionString=<connection-string>
Configurar o projeto
Pré-requisitos
Criar o aplicativo
Há duas funções no Azure Web PubSub, servidor e cliente. Esse conceito é semelhante às funções de servidor e cliente em um aplicativo Web. O servidor é responsável por gerenciar os clientes, escutar e responder às mensagens do cliente. O cliente é responsável por enviar e receber mensagens do usuário do servidor e visualizá-las para o usuário final.
Neste tutorial, vamos criar um aplicativo web de chat em tempo real. Em um aplicativo Web real, a responsabilidade do servidor também inclui a autenticação de clientes e o fornecimento de páginas da Web estáticas para a interface do usuário do aplicativo.
Usaremos o ASP.NET Core 8 para hospedar as páginas da web e lidar com as solicitações de entrada.
Primeiro, vamos criar um aplicativo web do ASP.NET Core em uma pasta chatapp
.
Crie um novo aplicativo Web.
mkdir chatapp cd chatapp dotnet new web
Adicione
app.UseStaticFiles()
no Program.cs para dar suporte à hospedagem de páginas da web estáticas.var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseStaticFiles(); app.Run();
Crie um arquivo HTML e salve-o como
wwwroot/index.html
. Vamos usá-lo mais tarde para a interface do usuário do aplicativo de chat.<html> <body> <h1>Azure Web PubSub Chat</h1> </body> </html>
Você pode testar o servidor executando dotnet run --urls http://localhost:8080
e acessando http://localhost:8080/index.html
no navegador.
Adicionar o ponto de extremidade de negociação
No tutorial Publicar e assinar mensagem, o assinante consome a cadeia de conexão diretamente. Em um aplicativo real, não é seguro compartilhar a cadeia de conexão com nenhum cliente, pois a cadeia de conexão tem alto privilégio para realizar qualquer operação no serviço. Agora, vamos fazer com que o servidor consuma a cadeia de conexão e exponha um ponto de extremidade negotiate
para o cliente obter a URL completa com o token de acesso. Dessa forma, o servidor pode adicionar o middleware de autenticação antes do ponto de extremidade negotiate
para impedir o acesso não autorizado.
Primeiro, instale as dependências.
dotnet add package Microsoft.Azure.WebPubSub.AspNetCore
Agora, vamos adicionar um ponto de extremidade /negotiate
para o cliente chamar para gerar o token.
using Azure.Core;
using Microsoft.Azure.WebPubSub.AspNetCore;
using Microsoft.Azure.WebPubSub.Common;
using Microsoft.Extensions.Primitives;
// Read connection string from environment
var connectionString = Environment.GetEnvironmentVariable("WebPubSubConnectionString");
if (connectionString == null)
{
throw new ArgumentNullException(nameof(connectionString));
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddWebPubSub(o => o.ServiceEndpoint = new WebPubSubServiceEndpoint(connectionString))
.AddWebPubSubServiceClient<Sample_ChatApp>();
var app = builder.Build();
app.UseStaticFiles();
// return the Client Access URL with negotiate endpoint
app.MapGet("/negotiate", (WebPubSubServiceClient<Sample_ChatApp> service, HttpContext context) =>
{
var id = context.Request.Query["id"];
if (StringValues.IsNullOrEmpty(id))
{
context.Response.StatusCode = 400;
return null;
}
return new
{
url = service.GetClientAccessUri(userId: id).AbsoluteUri
};
});
app.Run();
sealed class Sample_ChatApp : WebPubSubHub
{
}
AddWebPubSubServiceClient<THub>()
é usado para injetar o cliente de serviço WebPubSubServiceClient<THub>
, com o qual podemos usar na etapa de negociação para gerar token de conexão de cliente e em métodos de Hub para invocar APIs REST de serviço quando eventos de Hub são disparados. Esse código de geração de token é semelhante ao que usamos no tutorial de mensagem de publicação e assinatura, com a diferença de que passamos um argumento a mais (userId
) ao gerar o token. A ID de usuário pode ser usada para identificar a identidade do cliente, para que, ao receber uma mensagem, você saiba de onde ela vem.
O código lê a cadeia de conexão da variável de ambiente WebPubSubConnectionString
que definimos na etapa anterior.
Execute novamente o servidor usando dotnet run --urls http://localhost:8080
.
Você pode testar essa API acessando http://localhost:8080/negotiate?id=user1
e ele fornecerá a URL completa do Azure Web PubSub com um token de acesso.
Tratar eventos
No Azure Web PubSub, quando houver determinadas atividades acontecendo no lado do cliente (por exemplo, um cliente está se conectando, está conectado, desconectado ou um cliente está enviando mensagens), o serviço envia notificações ao servidor para que ele possa reagir a esses eventos.
Os eventos são entregues ao servidor na forma de webhook. O webhook é servido e exposto pelo servidor de aplicativos e registrado no lado do serviço do Azure Web PubSub. O serviço invoca os webhooks sempre que um evento acontece.
O Azure Web PubSub segue o CloudEvents para descrever os dados do evento.
Abaixo, lidamos com eventos do sistema connected
quando um cliente está conectado e lida com eventos de usuário message
quando um cliente está enviando mensagens para criar o aplicativo de chat.
O SDK do Web PubSub para AspNetCore Microsoft.Azure.WebPubSub.AspNetCore
que instalamos na etapa anterior também pode ajudar a analisar e processar as solicitações do CloudEvents.
Primeiro, adicione manipuladores de eventos antes de app.Run()
. Especifique o caminho do ponto de extremidade para os eventos, por exemplo /eventhandler
.
app.MapWebPubSubHub<Sample_ChatApp>("/eventhandler/{*path}");
app.Run();
Agora, dentro da classe Sample_ChatApp
que criamos na etapa anterior, adicione um construtor para trabalhar com WebPubSubServiceClient<Sample_ChatApp>
que usamos para invocar o serviço Web PubSub. E OnConnectedAsync()
para responder quando o evento connected
é disparado, OnMessageReceivedAsync()
para lidar com mensagens do cliente.
sealed class Sample_ChatApp : WebPubSubHub
{
private readonly WebPubSubServiceClient<Sample_ChatApp> _serviceClient;
public Sample_ChatApp(WebPubSubServiceClient<Sample_ChatApp> serviceClient)
{
_serviceClient = serviceClient;
}
public override async Task OnConnectedAsync(ConnectedEventRequest request)
{
Console.WriteLine($"[SYSTEM] {request.ConnectionContext.UserId} joined.");
}
public override async ValueTask<UserEventResponse> OnMessageReceivedAsync(UserEventRequest request, CancellationToken cancellationToken)
{
await _serviceClient.SendToAllAsync(RequestContent.Create(
new
{
from = request.ConnectionContext.UserId,
message = request.Data.ToString()
}),
ContentType.ApplicationJson);
return new UserEventResponse();
}
}
No código acima, usamos o cliente de serviço para transmitir uma mensagem de notificação no formato JSON a todos os quais está ingressado com SendToAllAsync
.
Atualizar a página da web
Agora, vamos atualizar index.html
para adicionar a lógica para conectar, enviar mensagem e exibir mensagens recebidas na página.
<html>
<body>
<h1>Azure Web PubSub Chat</h1>
<input id="message" placeholder="Type to chat...">
<div id="messages"></div>
<script>
(async function () {
let id = prompt('Please input your user name');
let res = await fetch(`/negotiate?id=${id}`);
let data = await res.json();
let ws = new WebSocket(data.url);
ws.onopen = () => console.log('connected');
let messages = document.querySelector('#messages');
ws.onmessage = event => {
let m = document.createElement('p');
let data = JSON.parse(event.data);
m.innerText = `[${data.type || ''}${data.from || ''}] ${data.message}`;
messages.appendChild(m);
};
let message = document.querySelector('#message');
message.addEventListener('keypress', e => {
if (e.charCode !== 13) return;
ws.send(message.value);
message.value = '';
});
})();
</script>
</body>
</html>
Você pode ver no código acima que nos conectamos usamos a API WebSocket nativa no navegador e usamos WebSocket.send()
para enviar mensagens e WebSocket.onmessage
para ouvir as mensagens recebidas.
Você também pode usar SDKs do cliente para se conectar ao serviço, possibilitando que você use a reconexão automática, tratamento de erros e muito mais.
Falta apenas uma etapa para o chat funcionar. Vamos configurar quais eventos são relevantes para nós e para onde enviar os eventos no serviço do Web PubSub.
Configurar o manipulador de eventos
Definimos o manipulador de eventos no serviço do Web PubSub para informar ao serviço para onde enviar os eventos.
Quando o servidor Web é executado localmente, como o serviço do Web PubSub invoca o localhost se ele não tem nenhum ponto de extremidade acessível pela Internet? Geralmente há duas maneiras. Uma é expor o localhost ao público usando alguma ferramenta de túnel geral. A outra forma é usar awps-tunnel para enviar por túnel o tráfego do serviço do Web PubSub por meio da ferramenta para o servidor local.
Nesta seção, usamos a CLI do Azure para definir os manipuladores de eventos e usar awps-tunnel para rotear o tráfego para localhost.
Definir as configurações do hub
Definimos o modelo de URL para usar o esquema tunnel
para que o Web PubSub encaminhe mensagens por meio da conexão de túnel do awps-tunnel
. Os manipuladores de eventos podem ser definidos no portal ou na CLI, conforme descrito neste artigo; definimos aqui por meio da CLI. Como ouvimos eventos no caminho /eventhandler
como conjuntos de etapas anteriores, definimos o modelo de URL como tunnel:///eventhandler
.
Use o comando az webpubsub hub create da CLI do Azure para criar as configurações do manipulador de eventos para o hub Sample_ChatApp
.
Importante
Substitua <your-unique-resource-name> pelo nome do recurso Web PubSub criado nas etapas anteriores.
az webpubsub hub create -n "<your-unique-resource-name>" -g "myResourceGroup" --hub-name "Sample_ChatApp" --event-handler url-template="tunnel:///eventhandler" user-event-pattern="*" system-event="connected"
Executar o awps-tunnel localmente
Baixar e instalar awps-tunnel
A ferramenta é executada no Node.js, versão 16 ou superior.
npm install -g @azure/web-pubsub-tunnel-tool
Usar a cadeia de conexão de serviço executar
export WebPubSubConnectionString="<your connection string>"
awps-tunnel run --hub Sample_ChatApp --upstream http://localhost:8080
Executar o servidor Web
Tudo está configurado. Vamos executar o servidor Web e testar o aplicativo de chat na prática.
Agora, execute o servidor usando dotnet run --urls http://localhost:8080
.
O exemplo de código completo deste tutorial pode ser encontrado aqui.
Abra o http://localhost:8080/index.html
. Você pode inserir seu nome de usuário e começar a conversar.
Autenticação lenta com o manipulador de eventos connect
Nas seções anteriores, demonstramos como usar o ponto de extremidade negotiate para retornar a URL de serviço do Web PubSub e o token de acesso JWT para que os clientes se conectem ao serviço Web PubSub. Em alguns casos, por exemplo, dispositivos de borda com recursos limitados, os clientes podem preferir se conectar diretamente aos recursos do Web PubSub. Nesses casos, você pode configurar o manipulador de eventos connect
para autenticar lentamente os clientes, atribuir ID de usuário aos clientes, especificar os grupos que os clientes ingressarem depois de se conectarem, configurar as permissões que os clientes têm e o subprotocolo WebSocket como a resposta WebSocket ao cliente, etc. Para detalhes, confira especificação do manipulador de eventos de conexão.
Agora, vamos usar o manipulador de eventos connect
para obter o semelhante ao que a seção negotiate faz.
Atualizar configurações do hub
Primeiro, vamos atualizar as configurações do hub para incluir também o manipulador de eventos connect
, precisamos também permitir a conexão anônima para que os clientes sem o token de acesso JWT possam se conectar ao serviço.
Use o comando az webpubsub hub update da CLI do Azure para criar as configurações do manipulador de eventos para o hub Sample_ChatApp
.
Importante
Substitua <your-unique-resource-name> pelo nome do recurso Web PubSub criado nas etapas anteriores.
az webpubsub hub update -n "<your-unique-resource-name>" -g "myResourceGroup" --hub-name "Sample_ChatApp" --allow-anonymous true --event-handler url-template="tunnel:///eventhandler" user-event-pattern="*" system-event="connected" system-event="connect"
Atualizar a lógica upstream para lidar com o evento de conexão
Agora, vamos atualizar a lógica upstream para lidar com o evento de conexão. Podemos também remover o ponto de extremidade negotiate agora.
Como semelhante ao que fazemos no ponto de extremidade negotiate como finalidade de demonstração, também lemos a ID dos parâmetros de consulta. No evento de conexão, a consulta de cliente original é preservada no corpo da solicitação de evento de conexão.
Dentro da classe Sample_ChatApp
, substitua OnConnectAsync()
para manipular o evento connect
:
sealed class Sample_ChatApp : WebPubSubHub
{
private readonly WebPubSubServiceClient<Sample_ChatApp> _serviceClient;
public Sample_ChatApp(WebPubSubServiceClient<Sample_ChatApp> serviceClient)
{
_serviceClient = serviceClient;
}
public override ValueTask<ConnectEventResponse> OnConnectAsync(ConnectEventRequest request, CancellationToken cancellationToken)
{
if (request.Query.TryGetValue("id", out var id))
{
return new ValueTask<ConnectEventResponse>(request.CreateResponse(userId: id.FirstOrDefault(), null, null, null));
}
// The SDK catches this exception and returns 401 to the caller
throw new UnauthorizedAccessException("Request missing id");
}
public override async Task OnConnectedAsync(ConnectedEventRequest request)
{
Console.WriteLine($"[SYSTEM] {request.ConnectionContext.UserId} joined.");
}
public override async ValueTask<UserEventResponse> OnMessageReceivedAsync(UserEventRequest request, CancellationToken cancellationToken)
{
await _serviceClient.SendToAllAsync(RequestContent.Create(
new
{
from = request.ConnectionContext.UserId,
message = request.Data.ToString()
}),
ContentType.ApplicationJson);
return new UserEventResponse();
}
}
Atualizar index.html para conectar-se diretamente
Agora, vamos atualizar a página da Web para conectar-se diretamente ao serviço Web PubSub. Uma coisa a mencionar é que, agora, para fins de demonstração, o ponto de extremidade de serviço do Web PubSub é codificado no código do cliente, atualize o nome do host <the host name of your service>
do serviço no html abaixo com o valor de seu próprio serviço. Pode ainda ser útil buscar o valor do ponto de extremidade de serviço do Web PubSub do servidor, oferecendo mais flexibilidade e controlabilidade para onde o cliente se conecta.
<html>
<body>
<h1>Azure Web PubSub Chat</h1>
<input id="message" placeholder="Type to chat...">
<div id="messages"></div>
<script>
(async function () {
// sample host: mock.webpubsub.azure.com
let hostname = "<the host name of your service>";
let id = prompt('Please input your user name');
let ws = new WebSocket(`wss://${hostname}/client/hubs/Sample_ChatApp?id=${id}`);
ws.onopen = () => console.log('connected');
let messages = document.querySelector('#messages');
ws.onmessage = event => {
let m = document.createElement('p');
let data = JSON.parse(event.data);
m.innerText = `[${data.type || ''}${data.from || ''}] ${data.message}`;
messages.appendChild(m);
};
let message = document.querySelector('#message');
message.addEventListener('keypress', e => {
if (e.charCode !== 13) return;
ws.send(message.value);
message.value = '';
});
})();
</script>
</body>
</html>
Executar novamente o servidor
Agora execute novamente o servidor e visite a página da Web seguindo as instruções anteriores. Se você tiver parado awps-tunnel
, execute novamente a ferramenta de túnel.
Próximas etapas
Este tutorial oferece uma ideia básica de como o sistema de eventos funciona no serviço do Azure Web PubSub.
Confira outros tutoriais para saber mais sobre como usar o serviço.