ASP.NET Core SignalR to biblioteka open-source upraszczająca dodawanie funkcji czasu rzeczywistego do aplikacji internetowych. Funkcje czasu rzeczywistego umożliwiają serwerowi natychmiastowe przesyłanie treści do klientów.
Dobre kandydatury dla SignalR:
Komponent sgcWebSockets SignalRCore używa WebSocket jako transportu do połączenia z serwerem SignalRCore. Jeżeli ten transport nie jest obsługiwany, zostanie zgłoszony błąd.
SignalRCore używa hubów do komunikacji między klientami a serwerami. SignalRCore udostępnia 2 protokoły hubowe: protokół tekstowy oparty na JSON oraz protokół binarny oparty na MessagePack. Komponent sgcWebSockets implementuje wyłącznie protokół tekstowy JSON do komunikacji z serwerami SignalRCore.
Aby skonfigurować Hub, z którego będzie korzystał klient, należy ustawić nazwę Huba we właściwości SignalRCore/Hub przed nawiązaniem połączenia klienta z serwerem.
Gdy klient otwiera nowe połączenie z serwerem, wysyła komunikat żądania zawierający protokół formatu i wersję. sgcWebSockets zawsze wysyła protokół formatu jako JSON. Serwer odpowie błędem, jeśli protokół nie jest przez niego obsługiwany; błąd ten można obsłużyć za pomocą zdarzenia OnSignalRCoreError. W przypadku nawiązania połączenia zostanie wywołane zdarzenie OnSignalRCoreConnect.
Gdy klient łączy się z serwerem SignalRCore, może wysłać ConnectionId identyfikujący klienta między sesjami, dzięki czemu po rozłączeniu klient może ponownie połączyć się z serwerem, podając ten sam wcześniejszy identyfikator połączenia. Aby uzyskać nowy identyfikator połączenia, należy połączyć się normalnie z serwerem; ConnectionId można poznać za pomocą zdarzenia OnBeforeConnectEvent. Aby ponownie połączyć się z serwerem i przekazać wcześniejszy identyfikator połączenia, należy użyć metody ReConnect i przekazać ConnectionId jako parametr.
Protokół SignalR to protokół dwukierunkowego RPC oparty na dowolnym transporcie obsługującym komunikaty. Obie strony połączenia mogą wywoływać procedury drugiej strony, a procedury mogą zwracać zero lub więcej wyników albo błąd. Przykład: klient może zażądać metody od serwera, a serwer może zażądać metody od klienta. Między serwerem a klientami wymieniane są następujące komunikaty:
HandshakeRequest: wysyłane przez klienta do serwera w celu uzgodnienia formatu wiadomości.
HandshakeResponse: serwer wysyła do klienta potwierdzenie poprzedniej wiadomości HandshakeRequest. Zawiera błąd, jeśli uzgadnianie połączenia nie powiodło się.
Close: wywoływane przez klienta lub serwer po zamknięciu połączenia. Zawiera błąd, jeśli połączenie zostało zamknięte z powodu błędu.
Wywołanie: klient lub serwer wysyła wiadomość do innego węzła w celu wywołania metody z argumentami lub bez.
StreamInvocation: klient lub serwer wysyła wiadomość do innego uczestnika w celu wywołania metody strumieniowej z argumentami lub bez. Odpowiedź zostanie podzielona na różne elementy.
StreamItem: jest odpowiedzią na poprzednie wywołanie StreamInvocation.
Completion: oznacza zakończenie poprzedniego wywołania lub StreamInvocation. Może zawierać wynik, jeśli proces zakończył się pomyślnie, lub błąd w przypadku wystąpienia problemu.
CancelInvocation: anuluj poprzednie żądanie StreamInvocation.
Ping: wiadomość służąca do sprawdzenia, czy połączenie jest nadal aktywne.
SignalRCore umożliwia stosowanie następujących kodowań:
Aktualnie obsługiwany jest wyłącznie format JSON, chociaż MessagePack może być używany przez kodowanie wysyłanych wiadomości za pomocą zewnętrznej biblioteki messagepack. Więcej informacji znajdziesz w sekcji MessagePack poniżej.
Konfiguracja protokołu kodowania jest zdefiniowana we właściwości SignalRCore.Protocol. Domyślną wartością jest srcpJSON.
Uwierzytelnianie można włączyć, aby powiązać użytkownika z każdym połączeniem i filtrować, którzy użytkownicy mogą uzyskiwać dostęp do zasobów. Uwierzytelnianie jest realizowane przy użyciu tokenów Bearer: klient dostarcza token dostępu, a serwer weryfikuje ten token i używa go do identyfikacji użytkownika.
W standardowych API sieci Web tokeny bearer są wysyłane w nagłówku HTTP, ale w przypadku WebSockets token jest przekazywany jako parametr ciągu zapytania.
Obsługiwane są następujące metody:
srcaRequestToken
Jeśli uwierzytelnianie jest włączone, przepływ wygląda następująco:
1. Najpierw próbuje uzyskać ważny token z serwera. Otwiera połączenie HTTP z Authentication.RequestToken.URL i wykonuje żądanie POST z danymi użytkownika i hasłem.
2. Jeśli poprzedni krok zakończy się sukcesem, zwracany jest token. W przeciwnym razie zwracany jest błąd.
3. Jeśli token zostanie zwrócony, otwierane jest nowe połączenie HTTP w celu negocjacji nowego połączenia. W tym miejscu token jest przekazywany jako nagłówek HTTP.
4. Jeśli poprzedni krok się powiedzie, otwierane jest połączenie WebSocket i token jest przekazywany jako parametr zapytania.
Authentication.Enabled: jeśli aktywna, autoryzacja będzie stosowana przed nawiązaniem połączenia WebSocket.
Authentication.Username: nazwa użytkownika przekazywana serwerowi w celu uwierzytelnienia.
Authentication.Password: tajne hasło podawane serwerowi w celu uwierzytelnienia.
Authentication.RequestToken.PostFieldUsername: nazwa pola do przekazania nazwy użytkownika (zależy od konfiguracji; należy sprawdzić stronę JavaScript HTTP, aby zobaczyć używaną nazwę).
Authentication.RequestToken.PostFieldPassword: nazwa pola, w którym przesyłane jest hasło (zależy od konfiguracji; należy sprawdzić stronę HTTP JavaScript, aby zobaczyć, która nazwa jest używana).
Authentication.RequestToken.URL: adres URL, pod którym żądany jest token.
Authentication.RequestToken.QueryFieldToken: nazwa parametru ciągu zapytania używanego przy połączeniu WebSocket.
srcaSetToken
Token jest przekazywany bezpośrednio do serwera SignalRCore (ponieważ token został uzyskany z innego serwera).
Authentication.Enabled: jeśli aktywne, autoryzacja będzie stosowana przed nawiązaniem połączenia WebSocket.
Authentication.SetToken.Token: uzyskana wartość tokenu.
Token dostępu może być wysyłany jako parametr zapytania (opcja domyślna) lub jako nagłówek HTTP w postaci tokenu Bearer. Właściwość Authentication.TokenParam służy do konfiguracji tego zachowania.
srcaBasic
Ta opcja korzysta z uwierzytelniania Basic Authentication i wymaga skonfigurowania komponentu SignalRCore oraz TsgcWebSocketClient.
Przykład: jeśli serwer wymaga uwierzytelniania podstawowego, a nazwa użytkownika to „user" i hasło to „secret", należy skonfigurować składniki w sposób przedstawiony poniżej.
// 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;
Istnieją trzy rodzaje interakcji między serwerem a klientami:
Wywołujący wysyła wiadomość do wywoływanego i oczekuje wiadomości informującej o zakończeniu wywołania oraz opcjonalnie wyniku wywołania
Przykład: klient wywołuje metodę SendMessage i przekazuje jako parametry nazwę użytkownika i treść wiadomości. Wysyła identyfikator wywołania, aby
otrzymać komunikat z wynikiem od serwera.
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;
Wywołujący wysyła wiadomość do wywołanego i nie oczekuje żadnych dalszych wiadomości dla tego wywołania. Wywołania mogą być wysyłane bez wartości identyfikatora wywołania. Oznacza to, że wywołanie jest „nieblokujące".
Przykład: klient wywołuje metodę SendMessage i przekazuje jako parametry nazwę użytkownika i treść wiadomości tekstowej. Klient nie oczekuje żadnej odpowiedzi od serwera na temat wyniku wywołania.
SignalRCore.Invoke('SendMessage', ['John', 'Hello All.']);
Wywołujący wysyła wiadomość do Wywoływanego i oczekuje jednego lub więcej wyników zwróconych przez Wywoływanego, a następnie wiadomości wskazującej koniec wywołania.
Przykład: klient wywołuje metodę Counter i żąda 10 liczb z interwałem 500 milisekund.
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;
Aby wykonać pojedyncze wywołanie, Caller stosuje następujący podstawowy przepływ:
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);
Należy przydzielić unikalną wartość Invocation ID (dowolny ciąg, wybrany przez wywołującego) reprezentującą wywołanie. Następnie wywołać metodę Invoke lub InvokeStream, przekazując Target, Arguments i InvocationId (brak InvocationId spowoduje brak wyniku zakończenia).
Jeśli wywołanie jest oznaczone jako nieblokujące (patrz "Wywołania nieblokujące" poniżej), należy tu zakończyć i natychmiast zwrócić sterowanie do aplikacji. Należy obsłużyć wiadomość StreamItem lub Completion z pasującym identyfikatorem wywołania.
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;
Można wykonać pojedyncze wywołanie i oczekiwać na jego zakończenie.
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;
Przypisz unikalną wartość identyfikatora wywołania (dowolny ciąg znaków wybrany przez wywołującego) do reprezentowania wywołania. Wywołaj metodę InvokeAndWait lub InvokeStreamAndWait z parametrami Target (cel wywołania), Arguments (argumenty) i InvocationId. Program będzie czekał do momentu wywołania zdarzenia zakończenia lub przekroczenia limitu czasu.
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;
Jeśli klient chce przestać odbierać wiadomości StreamItem przed wysłaniem przez serwer wiadomości Completion, może wysłać wiadomość CancelInvocation z tym samym InvocationId, który był używany w wiadomości StreamInvocation inicjującej strumień.
procedure OnSignalRCoreStreamItem(Sender: TObject; StreamItem: SignalRCore_StreamItem; var Cancel: Boolean);
begin
if StreamItem.InvocationId = 'id-000002' then
Cancel := True;
end;
Wywołanie jest uznawane za zakończone dopiero po odebraniu komunikatu Completion. Jeśli klient odbierze wywołanie z serwera, zostanie wywołane zdarzenie 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.');
Wysyłane przez klienta po zamknięciu połączenia. Zawiera powód błędu, jeśli połączenie zostało zamknięte z powodu błędu.
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;
Protokół SignalR Hub obsługuje komunikaty „Keep Alive" służące do utrzymania aktywności połączenia transportowego. Komunikaty te zapewniają:
Że serwery proxy nie zamykają połączenia w czasie bezczynności (gdy przesyłanych jest niewiele wiadomości). Jeżeli połączenie zostanie przerwane bez poprawnego zakończenia, aplikacja jest o tym informowana możliwie najszybciej.
Zachowanie keep-alive osiąga się przez wywołanie metody Ping lub włączenie HeartBeat na kliencie WebSocket. Jeżeli serwer wyśle ping do klienta, klient automatycznie wyśle odpowiedź i zostanie wywołane zdarzenie OnSignalRCoreKeepAlive.
procedure OnSignalRCoreKeepAlive(Sender: TObject);
begin
DoLog('#keepalive');
end;
W kodowaniu MsgPack protokołu SignalR każda wiadomość jest reprezentowana jako pojedyncza tablica MsgPack zawierająca elementy odpowiadające właściwościom danej wiadomości protokołu hub. Elementy tablicy mogą być wartościami prymitywnymi, tablicami (np. argumenty metody) lub obiektami (np. wartość argumentu). Pierwszym elementem tablicy jest typ wiadomości.
Aby dowiedzieć się, jak kodować wysyłane wiadomości, należy zapoznać się z dokumentacją MessagePack.
Za każdym razem, gdy odebrana zostaje nowa wiadomość, jest ona przekazywana w zdarzeniu OnSignalRCoreMessagePack. Wiadomość jest dostępna poprzez odczyt parametru strumienia Data. Parametr JSON jest domyślnie pusty; jeśli wiadomość MessagePack zostanie przekonwertowana na JSON, komponent przetworzy wiadomość JSON tak, jakby kodowanie używało JSON (a zatem zdarzenia OnSignalRCoreCompletion, OnSignalRCoreInvocation itp. zostaną wywołane).