O ASP.NET Core SignalR é uma biblioteca de código aberto que simplifica a adição de funcionalidades web em tempo real a aplicações. A funcionalidade web em tempo real permite que o código do lado do servidor envie conteúdo aos clientes instantaneamente.
Bons candidatos para o SignalR:
O componente sgcWebSockets SignalRCore utiliza WebSocket como transporte para se conectar a um servidor SignalRCore; se este transporte não for suportado, um erro será gerado.
SignalRCore utiliza hubs para comunicação entre clientes e servidores. SignalRCore fornece 2 protocolos de hub: protocolo de texto baseado em JSON e protocolo binário baseado em MessagePack. O componente sgcWebSockets implementa apenas o protocolo de texto JSON para comunicação com servidores SignalRCore.
Para configurar qual Hub o cliente utilizará, basta definir o nome do Hub na propriedade SignalRCore/Hub antes de o cliente se conectar ao servidor.
Quando um cliente abre uma nova conexão com o servidor, envia uma mensagem de requisição que contém o protocolo de formato e a versão. O sgcWebSockets sempre envia o protocolo de formato como JSON. O servidor responderá com um erro se o protocolo não for suportado por ele; este erro pode ser tratado utilizando o evento OnSignalRCoreError, e se a conexão for bem-sucedida, o evento OnSignalRCoreConnect será chamado.
Quando um cliente se conecta a um servidor SignalRCore, ele pode enviar um ConnectionId que identifica o cliente entre sessões, de modo que, se você tiver uma desconexão, o cliente possa reconectar ao servidor passando o mesmo connection id anterior. Para obter um novo connection id, basta conectar normalmente ao servidor e você pode saber o ConnectionId utilizando OnBeforeConnectEvent. Se você quiser reconectar ao servidor e passar um connection id anterior, utilize o método ReConnect e passe ConnectionId como parâmetro.
O Protocolo SignalR é um protocolo para RPC bidirecional sobre qualquer transporte baseado em mensagens. Qualquer uma das partes na conexão pode invocar procedimentos na outra parte, e os procedimentos podem retornar zero ou mais resultados ou um erro. Exemplo: o cliente pode solicitar um método do servidor e o servidor pode solicitar um método do cliente. As seguintes mensagens são trocadas entre o servidor e os clientes:
HandshakeRequest: o cliente envia ao servidor para acordar o formato da mensagem.
HandshakeResponse: o servidor responde ao cliente uma confirmação da mensagem HandshakeRequest anterior. Contém um erro se o handshake falhou.
Close: chamado pelo cliente ou servidor quando uma conexão é fechada. Contém um erro se a conexão foi fechada por causa de um erro.
Invocation: o cliente ou servidor envia uma mensagem a outro peer para invocar um método com ou sem argumentos.
StreamInvocation: o cliente ou o servidor envia uma mensagem a outro peer para invocar um método de streaming com ou sem argumentos. A Resposta será dividida em diferentes itens.
StreamItem: é uma resposta de um StreamInvocation anterior.
Completion: significa que uma invocation ou StreamInvocation anterior foi concluída. Pode conter um resultado se o processo foi bem-sucedido ou um erro se houver algum erro.
CancelInvocation: cancela uma requisição StreamInvocation anterior.
Ping: é uma mensagem para verificar se a conexão ainda está ativa.
O SignalRCore permite que você use as seguintes codificações:
Atualmente, apenas JSON é suportado, embora MessagePack possa ser utilizado codificando as mensagens enviadas por meio de uma biblioteca messagepack externa. Veja a seção MessagePack abaixo para mais informações.
A configuração do Protocolo de Codificação é definida na propriedade SignalRCore.Protocol. Por padrão, o valor é srcpJSON.
A autenticação pode ser habilitada para associar um usuário a cada conexão e filtrar quais usuários podem acessar recursos. A autenticação é implementada utilizando Bearer Tokens: o cliente fornece um access token e o servidor valida este token e o utiliza para identificar o usuário.
Em Web APIs padrão, os bearer tokens são enviados em um Cabeçalho HTTP, mas ao usar websockets, o token é transmitido como um parâmetro de query string.
Os seguintes métodos são suportados:
srcaRequestToken
Se a Autenticação estiver habilitada, o fluxo é:
1. Primeiro tenta obter um token válido do servidor. Abre uma conexão HTTP contra Authentication.RequestToken.URL e faz um POST utilizando os dados de User e Password.
2. Se a anterior for bem-sucedida, um token é retornado. Caso contrário, um erro é retornado.
3. Se um token for retornado, então abre uma nova conexão HTTP para negociar uma nova conexão. Aqui, o token é passado como um Cabeçalho HTTP.
4. Se o anterior for bem-sucedido, abre uma conexão websocket e passa o token como parâmetro de query string.
Authentication.Enabled: se ativo, a autorização será usada antes de uma conexão websocket ser estabelecida.
Authentication.Username: o nome de usuário fornecido ao servidor para autenticar.
Authentication.Password: a palavra secreta fornecida ao servidor para autenticar.
Authentication.RequestToken.PostFieldUsername: nome do campo para transmitir o username (depende da configuração, verifique a página javascript do http para ver qual nome é utilizado).
Authentication.RequestToken.PostFieldPassword: nome do campo para transmitir a senha (depende da configuração, verifique a página javascript http para ver qual nome é utilizado).
Authentication.RequestToken.URL: url onde o token é solicitado.
Authentication.RequestToken.QueryFieldToken: nome do parâmetro de query string utilizado na conexão websocket.
srcaSetToken
Aqui, você passa o token diretamente para o servidor SignalRCore (porque o token foi obtido de outro servidor).
Authentication.Enabled: se ativo, a autorização será utilizada antes de uma conexão websocket ser estabelecida.
Authentication.SetToken.Token: valor do token obtido.
O Access token pode ser enviado como parâmetro de query (esta é a opção padrão) ou enviado como cabeçalho HTTP como um Bearer Token. Use a propriedade Authentication.TokenParam para configurar este comportamento.
srcaBasic
Esta opção usa Basic Authentication, este método de autenticação requer a configuração do componente SignalRCore e do TsgcWebSocketClient.
Exemplo: se o servidor exige basic authentication e o nome de usuário é "user" e a senha é "secret", configure os componentes conforme mostrado abaixo.
// websocket client
WSClient := TsgcWebSocketClient.Create(nil);
WSClient.Authentication.Enabled := True;
WSClient.Authentication.Basic.Enabled := True;
WSClient.Authentication.URL.Enabled := False;
WSClient.Authentication.Session.Enabled := False;
WSClient.Authentication.Token.Enabled := False;
WSClient.Authentication.User := 'user';
WSClient.Authentication.Password := 'secret';
// signalrcore
Signal := TsgcWSAPI_SignalRCore.Create(nil);
Signal.SignalRCore.Authentication.Enabled := True;
Signal.SignalRCore.Authentication.Authentication := srcaBasic;
Signal.SignalRCore.Authentication.Username := 'user';
Signal.SignalRCore.Authentication.Password := 'secret';
Signal.Client := WSClient;
Há três tipos de interações entre servidor e clientes:
O Caller envia uma mensagem ao Callee e espera uma mensagem indicando que a invocação foi concluída e, opcionalmente, um resultado da invocação
Exemplo: o cliente invoca o método SendMessage e passa como parâmetros o nome de usuário e a mensagem de texto. Envia um Invocation Id para
obter uma mensagem de resultado do servidor.
SignalRCore.Invoke('SendMessage', ['John', 'Hello All.'], 'id-000001');
procedure OnSignalRCoreCompletion(Sender: TObject; Completion: TSignalRCore_Completion);
begin
if Completion.Error <> '' then
ShowMessage('Something goes wrong.')
else
ShowMessage('Invocation Successful!');
end;
O Caller envia uma mensagem ao Callee e não espera mais nenhuma mensagem para esta invocação. As invocações podem ser enviadas sem um valor de Invocation ID. Isto indica que a invocação é "non-blocking".
Exemplo: o cliente invoca o método SendMessage e passa como parâmetros o nome do usuário e a mensagem de texto. O cliente não espera nenhuma resposta do servidor sobre o resultado da invocação.
SignalRCore.Invoke('SendMessage', ['John', 'Hello All.']);
O Caller envia uma mensagem ao Callee e espera um ou mais resultados retornados pelo Callee, seguidos por uma mensagem indicando o fim da invocação.
Exemplo: o cliente invoca o método Counter e solicita 10 números com um intervalo de 500 milissegundos.
SignalRCore.InvokeStream('Counter', [10, 500], 'id-000002');
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: TSignalRCore_StreamItem; var Cancel: Boolean);
begin
DoLog('#stream item: ' + StreamItem.Item);
end;
procedure OnSignalRCoreCompletion(Sender: TObject; Completion: TSignalRCore_Completion);
begin
if Completion.Error '' then
ShowMessage('Something goes wrong.')
else
ShowMessage('Invocation Successful!');
end;
Para realizar uma única invocação, o Caller segue o seguinte fluxo básico:
procedure Invoke(const aTarget: String; const aArguments: Array of Const; const aInvocationId: String = '');
procedure InvokeStream(const aTarget: String; const aArguments: Array of Const; const aInvocationId: String);
Aloque um valor único de Invocation ID (string arbitrária, escolhida pelo Caller) para representar a invocação. Chame o método Invoke ou InvokeStream contendo o Target sendo invocado, Arguments e InvocationId (se você não enviar InvocationId, não obterá o resultado de conclusão).
Se a Invocation estiver marcada como não-bloqueante (consulte "Non-Blocking Invocations" abaixo), pare aqui e retorne imediatamente o controle à aplicação. Trate a mensagem StreamItem ou Completion com um Invocation ID correspondente.
SignalRCore.InvokeStream('Counter', [10, 500], 'id-000002');
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: TSignalRCore_StreamItem; var Cancel: Boolean);
begin
if StreamItem.InvocationId = 'id-000002' then
DoLog('#stream item: ' + StreamItem.Item);
end;
procedure OnSignalRCoreCompletion(Sender: TObject; Completion: TSignalRCore_Completion);
begin
if StreamItem.InvocationId = 'id-000002' then
begin
if Completion.Error '' then
ShowMessage('Something goes wrong.')
else
ShowMessage('Invocation Successful!');
end;
end;
Você pode realizar uma única invocação e aguardar a conclusão.
function InvokeAndWait(const aTarget: String; aArguments: Array of Const; aInvocationId: String; out Completion: TSignalRCore_Completion;
const aTimeout: Integer = 10000): Boolean;
function InvokeStreamAndWait(const aTarget: String; const aArguments: Array of Const; const aInvocationId: String;
out Completion: TSignalRCore_Completion; const aTimeout: Integer = 10000): Boolean;
Aloque um valor de Invocation ID único (string arbitrária, escolhida pelo Caller) para representar a invocação. Chame o método InvokeAndWait ou InvokeStreamAndWait contendo o Target sendo invocado, os Arguments e o InvocationId. O programa aguardará até que o evento de conclusão seja chamado ou o Time out seja excedido.
var
oCompletion: TSignalRCore_Completion;
begin
if SignalRCore.InvokeStreamAndWait('Counter', [10, 500], 'id-000002', oCompletion) then
DoLog('#invoke stream ok: ' + oCompletion.Result)
else
DoLog('#invocke stream error: ' + oCompletion.Error);
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: TSignalRCore_StreamItem; var Cancel: Boolean);
begin
if StreamItem.InvocationId = 'id-000002' then
DoLog('#stream item: ' + StreamItem.Item);
end;
Se o cliente quiser parar de receber mensagens StreamItem antes que o Servidor envie uma mensagem Completion, o cliente pode enviar uma mensagem CancelInvocation com o mesmo InvocationId utilizado na mensagem StreamInvocation que iniciou o stream.
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: SignalRCore_StreamItem; var Cancel: Boolean);
begin
if StreamItem.InvocationId = 'id-000002' then
Cancel := True;
end;
Uma Invocation só é considerada concluída quando a mensagem Completion é recebida. Se o cliente receber uma Invocation do servidor, o evento OnSignalRCoreInvocation será chamado.
procedure OnSignalRCoreInvocation(Sender: TObject; Invocation: TSignalRCore_Invocation);
begin
if Invocation.Target = 'SendMessage' then
... your code here ...
end;
// Once invocation is completed, call Completion method to inform server invocation is finished.
// If result is successful, then call CompletionResult method:
SignalRCore.CompletionResult('id-000002', 'ok');
// If not, then call CompletionError method:
SignalRCore.CompletionError('id-000002', 'Error processing invocation.');
Enviado pelo cliente quando uma conexão é fechada. Contém um motivo de erro se a conexão foi fechada por causa de um erro.
SignalRCore.Close('Unexpected message').
// If the server close connection by any reason, OnSignalRCoreClose event will be called.
procedure OnSignalRCoreClose(Sender: TObject; Close: TSignalRCore_Close);
begin
DoLog('#closed: ' + Close.Error);
end;
O protocolo SignalR Hub suporta mensagens "Keep Alive" utilizadas para garantir que a conexão de transporte subjacente permaneça ativa. Essas mensagens ajudam a garantir:
Que proxies não fechem a conexão subjacente durante períodos ociosos (quando poucas mensagens estão sendo enviadas). Se a conexão subjacente for derrubada sem ser encerrada graciosamente, a aplicação é informada o mais rápido possível.
O comportamento keep alive é obtido chamando o método Ping ou habilitando o HeartBeat no cliente WebSocket. Se o servidor enviar um ping ao cliente, o cliente enviará automaticamente uma resposta e o evento OnSignalRCoreKeepAlive será chamado.
procedure OnSignalRCoreKeepAlive(Sender: TObject);
begin
DoLog('#keepalive');
end;
Na Codificação MsgPack do Protocolo SignalR, cada Mensagem é representada como um único array MsgPack contendo itens que correspondem às propriedades da mensagem do hub protocol fornecido. Os itens do array podem ser valores primitivos, arrays (por exemplo, argumentos de método) ou objetos (por exemplo, valor de argumento). O primeiro item do array é o tipo da mensagem.
Consulte a documentação do MessagePack para ver como codificar as mensagens enviadas.
Toda vez que uma nova mensagem é recebida, ela é despachada no evento OnSignalRCoreMessagePack. A mensagem pode ser acessada lendo o parâmetro Data Stream. O parâmetro JSON está vazio por padrão; se você converter a mensagem MessagePack em JSON, o componente processará a mensagem JSON como se a codificação estivesse utilizando JSON (de modo que os eventos OnSignalRCoreCompletion, OnSignalRCoreInvocation... serão despachados).