ASP.NET Core SignalR è una libreria open source che semplifica l'aggiunta di funzionalità web in tempo reale alle app. La funzionalità web in tempo reale consente al codice lato server di inviare contenuti ai client istantaneamente.
Buoni candidati per SignalR:
Il componente SignalRCore di sgcWebSockets utilizza WebSocket come trasporto per connettersi a un server SignalRCore; se questo trasporto non è supportato, verrà generato un errore.
SignalRCore utilizza hub per la comunicazione tra client e server. SignalRCore fornisce 2 protocolli hub: il protocollo testuale basato su JSON e il protocollo binario basato su MessagePack. Il componente sgcWebSockets implementa solo il protocollo testuale JSON per comunicare con i server SignalRCore.
Per configurare quale Hub il client utilizzerà, impostare nella proprietà SignalRCore/Hub il nome dell'Hub prima che il client si connetta al server.
Quando un client apre una nuova connessione al server, invia un messaggio di richiesta contenente il protocollo e la versione del formato. sgcWebSockets invia sempre il protocollo di formato come JSON. Il server risponderà con un errore se il protocollo non è supportato; questo errore può essere gestito tramite l'evento OnSignalRCoreError, e se la connessione ha successo, verrà chiamato l'evento OnSignalRCoreConnect.
Quando un client si connette a un server SignalRCore, può inviare un ConnectionId che identifica il client tra le sessioni, in modo che in caso di disconnessione il client possa riconnettersi al server passando lo stesso ConnectionId precedente. Per ottenere un nuovo ConnectionId, basta connettersi normalmente al server e si può conoscere il ConnectionId tramite OnBeforeConnectEvent. Se si desidera riconnettersi al server e passare un ConnectionId precedente, utilizzare il metodo ReConnect e passare ConnectionId come parametro.
Il protocollo SignalR è un protocollo per RPC bidirezionale su qualsiasi trasporto basato su messaggi. Entrambe le parti nella connessione possono invocare procedure sull'altra parte, e le procedure possono restituire zero o più risultati oppure un errore. Esempio: il client può richiedere un metodo al server e il server può richiedere un metodo al client. I seguenti messaggi vengono scambiati tra server e client:
HandshakeRequest: il client invia al server per concordare il formato dei messaggi.
HandshakeResponse: il server risponde al client con una conferma del messaggio HandshakeRequest precedente. Contiene un errore se l'handshake non è riuscito.
Close: chiamato dal client o dal server quando una connessione viene chiusa. Contiene un errore se la connessione è stata chiusa a causa di un errore.
Invocazione: il client o il server invia un messaggio a un altro peer per invocare un metodo con o senza argomenti.
StreamInvocation: il client o il server invia un messaggio a un altro peer per invocare un metodo in streaming con o senza argomenti. La risposta verrà suddivisa in elementi distinti.
StreamItem: è una risposta a una precedente StreamInvocation.
Completion: indica che una precedente invocazione o StreamInvocation è stata completata. Può contenere un risultato se il processo è andato a buon fine o un errore in caso di problemi.
CancelInvocation: annulla una precedente richiesta StreamInvocation.
Ping: è un messaggio per verificare se la connessione è ancora attiva.
SignalRCore Le consente di utilizzare le seguenti codifiche:
Attualmente è supportato solo JSON, sebbene sia possibile utilizzare MessagePack codificando i messaggi inviati tramite una libreria messagepack esterna. Veda la sezione MessagePack di seguito per maggiori informazioni.
La configurazione del protocollo di codifica è definita nella proprietà SignalRCore.Protocol. Per impostazione predefinita il valore è srcpJSON.
L'autenticazione può essere abilitata per associare un utente a ogni connessione e filtrare quali utenti possono accedere alle risorse. L'autenticazione viene implementata tramite Bearer Token: il client fornisce un token di accesso e il server convalida questo token e lo utilizza per identificare l'utente.
Nelle API Web standard, i bearer token vengono inviati in un Header HTTP, ma quando si utilizzano i websocket, il token viene trasmesso come parametro della query string.
Sono supportati i seguenti metodi:
srcaRequestToken
Se l'autenticazione è abilitata, il flusso è:
1. Prima tenta di ottenere un token valido dal server. Apre una connessione HTTP verso Authentication.RequestToken.URL ed esegue un POST utilizzando i dati di utente e password.
2. Se l'operazione precedente ha avuto successo, viene restituito un token. In caso contrario, viene restituito un errore.
3. Se viene restituito un token, allora apre una nuova connessione HTTP per negoziare una nuova connessione. Qui, il token viene passato come header HTTP.
4. Se l'operazione precedente ha esito positivo, apre una connessione WebSocket e passa il token come parametro della query string.
Authentication.Enabled: se attivo, l'autorizzazione verrà utilizzata prima che venga stabilita una connessione websocket.
Authentication.Username: il nome utente fornito al server per l'autenticazione.
Authentication.Password: la parola segreta fornita al server per l'autenticazione.
Authentication.RequestToken.PostFieldUsername: nome del campo per trasmettere il nome utente (dipende dalla configurazione, verificare la pagina JavaScript HTTP per vedere quale nome viene utilizzato).
Authentication.RequestToken.PostFieldPassword: nome del campo per trasmettere la password (dipende dalla configurazione, verificare la pagina JavaScript HTTP per vedere quale nome viene utilizzato).
Authentication.RequestToken.URL: url in cui viene richiesto il token.
Authentication.RequestToken.QueryFieldToken: nome del parametro della stringa di query utilizzato nella connessione WebSocket.
srcaSetToken
Qui, si passa il token direttamente al server SignalRCore (perché il token è stato ottenuto da un altro server).
Authentication.Enabled: se attivo, verrà utilizzata l'autorizzazione prima che venga stabilita una connessione websocket.
Authentication.SetToken.Token: valore del token ottenuto.
L'Access token può essere inviato come parametro di query (questa è l'opzione predefinita) oppure inviato come HTTP Header come Bearer Token. Utilizzi la proprietà Authentication.TokenParam per configurare questo comportamento.
srcaBasic
Questa opzione utilizza l'autenticazione di base; questo metodo di autenticazione richiede la configurazione del componente SignalRCore e del TsgcWebSocketClient.
Esempio: se il server richiede l'autenticazione di base e il nome utente è "user" e la password è "secret", configurare i componenti come mostrato di seguito.
// 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;
Esistono tre tipi di interazioni tra server e client:
Il Caller invia un messaggio al Callee e si aspetta un messaggio che indichi che l'invocazione è stata completata e, facoltativamente, un risultato dell'invocazione
Esempio: il client invoca il metodo SendMessage e passa come parametri il nome utente e il messaggio di testo. Invia un ID invocazione per
ottenere un messaggio di risultato dal server.
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;
Il Caller invia un messaggio al Callee e non si aspetta ulteriori messaggi per questa invocazione. Le invocazioni possono essere inviate senza un valore di Invocation ID, il che indica che l'invocazione è "non bloccante".
Esempio: il client invoca il metodo SendMessage e passa come parametri il nome utente e il messaggio di testo. Il client non si aspetta alcuna risposta dal server riguardo al risultato dell'invocazione.
SignalRCore.Invoke('SendMessage', ['John', 'Hello All.']);
Il Caller invia un messaggio al Callee e si aspetta uno o più risultati restituiti dal Callee seguiti da un messaggio che indica la fine dell'invocazione.
Esempio: il client invoca il metodo Counter e richiede 10 numeri con un intervallo di 500 millisecondi.
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;
Per eseguire una singola invocazione, il Caller segue il seguente flusso di base:
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);
Allocare un valore univoco di ID invocazione (stringa arbitraria, scelta dal chiamante) per rappresentare l'invocazione. Chiamare il metodo Invoke o InvokeStream specificando il Target invocato, gli Arguments e l'InvocationId (se non si invia l'InvocationId, non si riceverà il risultato di completamento).
Se l'invocazione è contrassegnata come non bloccante (vedere "Invocazioni Non Bloccanti" di seguito), ci si fermi qui e si restituisca immediatamente il controllo all'applicazione. Gestire il messaggio StreamItem o Completion con un ID Invocazione corrispondente.
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;
È possibile effettuare una singola chiamata e attendere il completamento.
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;
Assegni un valore Invocation ID univoco (stringa arbitraria, scelta dal Caller) per rappresentare l'invocazione. Chiami il metodo InvokeAndWait o InvokeStreamAndWait contenente il Target invocato, gli Arguments e l'InvocationId. Il programma attenderà finché non viene chiamato l'evento di completamento oppure è stato superato il Time out.
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 il client desidera smettere di ricevere messaggi StreamItem prima che il server invii un messaggio Completion, il client può inviare un messaggio CancelInvocation con lo stesso InvocationId utilizzato per il messaggio StreamInvocation che ha avviato lo stream.
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: SignalRCore_StreamItem; var Cancel: Boolean);
begin
if StreamItem.InvocationId = 'id-000002' then
Cancel := True;
end;
Un'invocazione è considerata completata solo quando viene ricevuto il messaggio di completamento. Se il client riceve un'invocazione dal server, verrà chiamato l'evento OnSignalRCoreInvocation.
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.');
Inviato dal client quando una connessione viene chiusa. Contiene un motivo di errore se la connessione è stata chiusa a causa di un errore.
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;
Il protocollo SignalR Hub supporta messaggi "Keep Alive" utilizzati per garantire che la connessione di trasporto sottostante rimanga attiva. Questi messaggi aiutano a garantire:
I proxy non chiudono la connessione sottostante durante i periodi di inattività (quando vengono inviati pochi messaggi). Se la connessione sottostante viene interrotta senza essere terminata correttamente, l'applicazione viene informata il prima possibile.
Il comportamento keep alive viene ottenuto chiamando il metodo Ping o abilitando HeartBeat sul client WebSocket. Se il server invia un ping al client, il client invierà automaticamente una risposta e l'evento OnSignalRCoreKeepAlive verrà chiamato.
procedure OnSignalRCoreKeepAlive(Sender: TObject);
begin
DoLog('#keepalive');
end;
Nella codifica MsgPack del protocollo SignalR, ogni messaggio è rappresentato come un singolo array MsgPack contenente elementi che corrispondono alle proprietà del messaggio del protocollo hub specificato. Gli elementi dell'array possono essere valori primitivi, array (ad es. argomenti del metodo) o oggetti (ad es. valore dell'argomento). Il primo elemento dell'array è il tipo di messaggio.
Faccia riferimento alla documentazione di MessagePack per vedere come codificare i messaggi inviati.
Ogni volta che viene ricevuto un nuovo messaggio, questo viene inviato nell'evento OnSignalRCoreMessagePack. È possibile accedere al messaggio leggendo il parametro Data Stream. Il parametro JSON è vuoto per impostazione predefinita; se si converte il messaggio MessagePack in JSON, il componente elaborerà il messaggio JSON come se la codifica fosse JSON (quindi verranno generati gli eventi OnSignalRCoreCompletion, OnSignalRCoreInvocation...).