Há algum tempo eu comentei sobre a possibilidade do WCF expor serviços para serem consumidos utilizando a tecnologia de Web Sockets. Neste mesmo artigo eu falei sobre os detalhes do funcionamento deste protocolo, e que se quiser saber o seu funcionamento e implementação em um nível mais baixo, aconselho a leitura.
Como sabemos, o WCF é uma tecnologia que roda do lado do servidor. Da mesma forma que temos o ASP.NET Web API, que é uma tecnologia que utilizamos para criarmos APIs e expor para os mais diversos tipos de clientes. Assim como a Microsoft adicionou o suporte à web sockets no WCF, ela também fez o mesmo com o ASP.NET Web API e MVC, ou seja, incorporou no próprio framework o suporte para criação de recursos que são expostos utilizando web sockets.
É importante dizer que a Microsoft também criou uma outra tecnologia chamada de SignalR, que fornece diversos recursos para a criação de aplicações que precisam gerar e consumir informações que são consideradas de tempo real. Este framework já possui classes que abstraem a complexidade de exposição destes tipos de serviços, fornecendo classes de mais alto nível para trabalho.
Entre os novos tipos que foram adicionados, temos a propriedade IsWebSocketRequest (exposta pela classe HttpContext), que retorna um valor boleano indicando se a requisição está solicitando a migração do protocolo para web sockets. Caso seja verdadeiro, então recorremos ao método AcceptWebSocketRequest da mesma classe para especificarmos a função que irá periodicamente ser executada.
Para o exemplo, vamos criar um publicador de notícias, que quando o cliente optar por assiná-lo, ele irá receber as notícias de forma randômica em um banner a cada 2 segundos. Como podemos ver no código abaixo, o método Assinar da API é exposto para que os clientes cheguem até ele através do método GET do HTTP. Como se trata de uma solicitação de migração, retornamos o código 101 do HTTP indicando a troca do protocolo para web sockets.
public class PublicadorController : ApiController
{
private static IList<string> noticiais;
private static Random random;
static PublicadorController()
{
noticiais = new List<string>()
{
“You can buy a fingerprint reader keyboard for your Surface Pro 3”,
“Microsoft’s new activity tracker is the $249 Microsoft Band”,
“Microsoft’s Lumia 950 is the new flagship Windows phone”,
“Windows 10 will start rolling out to phones in December”,
“Microsoft’s Panos Panay is pumped about everything (2012–present)”
};
random = new Random();
}
[HttpGet]
public HttpResponseMessage Assinar()
{
var httpContext = Request.Properties[“MS_HttpContext”] as HttpContextBase;
if (httpContext.IsWebSocketRequest)
httpContext.AcceptWebSocketRequest(EnviarNoticias);
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
private async Task EnviarNoticias(AspNetWebSocketContext context)
{
var socket = context.WebSocket;
while (true)
{
await Task.Delay(2000);
if (socket.State == WebSocketState.Open)
{
var noticia = noticiais[random.Next(0, noticiais.Count – 1)];
var buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(noticia));
await socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
}
else
{
break;
}
}
}
}
Já dentro do método EnviarNoticias criamos um laço infinito que fica selecionando randomicamente uma das notícias do repositório e enviando para o(s) cliente(s) conectado(s). É claro que também podemos receber informações dos clientes conectados. Para o exemplo estou me limitando a apenas gerar o conteúdo de retorno, sem receber nada. Se quiser, pode utilizar o método ReceiveAsync da classe WebSocket para ter acesso à informação enviada por ele. E, por fim, para não ficar eternamente rodando o código, avaliamos a cada iteração se o estado da conexão ainda continua aberto, caso contrário, encerramos o mesmo.
E do lado do cliente, se for uma aplicação web, vamos recorrer ao código Javascript para consumir este serviço. Temos também a disposição deste lado a classe WebSocket, que quando instanciada, devemos informar o endereço para o método que assina e migra o protocolo para web sockets. Repare que a instância é criada dentro do evento click do botão Conectar e também já nos associamos aos eventos – autoexplicativos – onopen, onmessage e onclose (e ainda tem o onerror).
<!DOCTYPE html>
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<title></title>
https://code.jquery.com/jquery-2.1.4.min.js
var ws;
$().ready(function ()
{
$(“#Conectar”).click(function () {
ws = new WebSocket(“ws://localhost:2548/api/Publicador/Assinar”);
ws.onopen = function () {
$(“#Status”).text(“Conectado. Recuperando Notícias…”);
};
ws.onmessage = function (evt) {
$(“#Status”).text(“”);
$(“#Banner”).text(evt.data);
};
ws.onclose = function () {
$(“#Banner”).text(“”);
$(“#Status”).text(“Desconectado”);
};
});
$(“#Desconectar”).click(function () {
ws.close();
});
});
</head>
<body>
<input type=”button” value=”Conectar” id=”Conectar” />
<input type=”button” value=”Desconectar” id=”Desconectar” />
<br /><br />
<span id=”Status”></span>
<span id=”Banner”></span>
</body>
</html>
Quando acessar a página HTML e clicar no botão Conectar, o resultado é apresentado abaixo. A qualquer momento podemos pressionar no botão Desconectar e indicar ao servidor que não estamos mais interessados no retorno das informações. A cada envio de mensagem do servidor para o cliente, o evento onmessage é disparado, e estamos exibindo a mensagem em um campo na tela.
Apenas para informação, o Fiddler é capaz de identificar quando a conexão é migrada para web sockets, e a partir daí consegue capturar as mensagens que são enviados para o cliente. A imagem abaixo é possível visualizar o log capturado depois da assinatura realizada: