#1 2024-10-26 18:22:30

dcoun
Member
From: Crete, Greece
Registered: 2020-02-18
Posts: 430

ClientRestoreCallbacks in TWebSocketProcessSettings

when do we need ClientRestoreCallbacks to have a true value?

I am trying to tune a websocket server to be able to find when a websocket client has closed the connection.
IServiceWithCallbackReleased is ok for clients that close the connection as it should be closed
But when a client looses its connection (due to network or other reasons), the server can not be informed about it, or I am missing how to implemented.

Any ideas? thank you in advance

Offline

#2 2024-10-27 04:14:27

zen010101
Member
Registered: 2024-06-15
Posts: 70

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

You can refer to this example:  https://github.com/synopse/mORMot/blob/ … Server.dpr

However, CallbackReleased can only be automatically triggered when the Client is closed normally (Interfaces are freed normally) and the network is ok. If it is due to abnormal reasons, such as the client program being killed by the OS, then this callback function will not be called.

I asked a similar question here:  https://synopse.info/forum/viewtopic.php?id=7037

After research, my solution is as follows:

First, create a subclass of TRestServer and add the callback function DoWSClose:

  TMyServer = class(TRestServerFullMemory)
  public
    procedure DoWSClose(Protocol: TWebSocketProtocol);
  end;
...
procedure TMyServer.DoWSClose(Protocol: TWebSocketProtocol);
begin
  Writeln('ConnectID: ', Protocol.ConnectionID, ' is closed. IP: ', Protocol.RemoteIP);
end;

In this callback function, you can obtain ConnectionID, RemoteIP and other information through Protocol parameters.

Second, pass in the new DoWSClose callback function in the WebSocketEnable method call:

  Server := TMyServer.CreateWithOwnModel([]);
  try
    Server.Server.CreateMissingTables;
    Server.ServiceDefine(TChatService,[IChatService],sicShared).
      SetOptions([],[optExecLockedPerInterface]). // thread-safe fConnected[]
      ByPassAuthentication := true;
    HttpServer := TRestHttpServer.Create('8888',[Server],'+',useBidirSocket);
    try
      HttpServer.WebSocketsEnable(Server,PROJECT31_TRANSMISSION_KEY, false,
      [pboSynLzCompress], nil, Server.DoWSClose).
        SetFullLog; // full verbose logs for this demo
...

Eventually, you will find that no matter what reason the Client disconnects from the Server, this callback function will be called. Of course, you need to bind the relationship between RemoteIP and Client Callback Interface and any other data you need to centrally manage in Join() or other similar Register() services. This way, when you are informed of the interruption, you gain control over handling the objects in the relevant list. Additionally, you should pay attention to thread-safety issues.

Offline

#3 2024-10-27 16:55:14

dcoun
Member
From: Crete, Greece
Registered: 2020-02-18
Posts: 430

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

Thank you a lot for the response.

My main problem is to transform this "Protocol: TWebSocketProtocol" to the callback interface that it is created in the example.
I have found that the callbacks are of type TInterfacedObjectFakeServer are kept in TServiceContainerServer.fFakeCallbacks in the server, but it is much complicated for me with existing knowledge of mormot
And the ClientRestoreCallbacks  above can update the connectionID (type TRestConnectionID or THttpServerConnectionID ?) and I am not sure if TOnWebSocketProtocolClosed event should be used
I need more documentation of the idea behind this process

Offline

#4 2024-10-27 17:05:29

zen010101
Member
Registered: 2024-06-15
Posts: 70

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

You should take a look at this example first: https://github.com/synopse/mORMot/blob/ … Server.dpr

The documentation for this example is here: https://synopse.info/files/html/Synopse … l#TITL_173

Offline

#5 2024-10-27 17:20:30

dcoun
Member
From: Crete, Greece
Registered: 2020-02-18
Posts: 430

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

Documentation is more detailed than the example and I have followed the direction to keep an array of the callback interfaces.
Still the problem is how to be notified that a client has been disconnected and the callback interface is invalid without trying to send it a message.
I am not also sure of  what to do to keep the connection, except for polling messages.

Offline

#6 2024-10-27 17:31:01

zen010101
Member
Registered: 2024-06-15
Posts: 70

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

The following method will be called by the server, when a client callback instance is released (either explicitly, or if the connection is broken), so could be used to unsubscribe to the notification, simply by deleting the callback from the internal fConnected[] array:

procedure TChatService.CallbackReleased(const callback: IInvokable; const interfaceName: RawUTF8);
begin
  if interfaceName='IChatCallback' then
    InterfaceArrayDelete(fConnected,callback);
end;

The framework will in fact recognize the following method definition in any interface type for a service (it will check the method name, and the method parameters):

   procedure CallbackReleased(const callback: IInvokable; const interfaceName: RawUTF8);

When a callback interface parameter (in our case, IChatCallback) will be released on the client side, this method will be called with the corresponding interface instance and type name as parameters. You do not have to call explicitly any method on the client side to unsubscribe a service: assigning nil to a callback variable, or freeing the class instance owning it as a field on the subscriber side, will automatically unregister it on the publisher side.

This is the answer given in the documentation. https://synopse.info/files/html/Synopse … l#TITL_173

Offline

#7 2024-10-27 17:32:58

dcoun
Member
From: Crete, Greece
Registered: 2020-02-18
Posts: 430

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

Yes, exactly, this the process for a client closing normaly. I am using it. I was wondering if there is a way to identify clients closed abormally

Offline

#8 2024-10-27 17:46:38

zen010101
Member
Registered: 2024-06-15
Posts: 70

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

In #2 floor, I mentioned one method:

      HttpServer.WebSocketsEnable(Server,PROJECT31_TRANSMISSION_KEY, false,
      [pboSynLzCompress], nil, Server.DoWSClose);


That is, inject the parameter aOnWSClosed: TOnWebSocketProtocolClosed in WebSocketsEnable();

In addition, there are two event properties in PWebSocketProcessSettings returned by WebSocketsEnable() that can also be used for your needs:

    /// callback run when a WebSockets client is just connected
    // - triggerred by TWebSocketProcess.ProcessStart
    OnClientConnected: TNotifyEvent;
    /// callback run when a WebSockets client is just disconnected
    // - triggerred by TWebSocketProcess.ProcessStop
    OnClientDisconnected: TNotifyEvent;
    /// if the WebSockets Client should be upgraded after socket reconnection 

Now that you have these events, they will be automatically called, and when the connection between the client and the server is abnormally interrupted, all you need to do in advance is to establish the correlation between the RemoteIP and the Callback 。

Offline

#9 2024-10-27 17:51:10

dcoun
Member
From: Crete, Greece
Registered: 2020-02-18
Posts: 430

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

I have a lot of clients behind a firewall and I can see one IP for multiple clients. It does not helps
I was thinking to add a custom header for each client, to keep with the callback interface, but I was wondering if there is an other way through mormot framework

Last edited by dcoun (2024-10-27 17:52:55)

Offline

#10 2024-10-28 02:51:33

zen010101
Member
Registered: 2024-06-15
Posts: 70

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

If you have trouble identifying a specific client using RemoteIP, I think you can use ConnectionID to do it.

In the case of Project31ChatClient sample routine, we can use the following code to obtain the Client ConnectionID:

procedure Run;
var Client: TRestHttpClientWebsockets;
    pseudo,msg: string;
    Service: IChatService;
    callback: IChatCallback;
begin
    ...
    Client.WebSocketsUpgrade(PROJECT31_TRANSMISSION_KEY);
    ...
    Client.ServiceDefine([IChatService],sicShared);
    if not Client.Services.Resolve(IChatService,Service) then
      raise EServiceException.Create('Service IChatService unavailable');
    try
      ...
      callback := TChatCallback.Create(Client,IChatCallback);
      Service.Join(pseudo,callback);
      ConsoleWrite('ConnectionID: %', [Client.WebSockets.WebSockets.ConnectionID], ccGreen);   // LOOK AT HERE
      ...
    finally
      callback := nil; // will unsubscribe from the remote publisher
      Service := nil;  // release the service local instance BEFORE Client.Free
    end;
  finally
    Client.Free;
  end;
end;

Now that you have the ConnectionID, pass it to the server when joining() or other Register-alike() service, then on the server side, you can establish the corresponding relationship between the ConnectionID and the client Callback Interface.

Offline

#11 2024-10-28 06:13:53

dcoun
Member
From: Crete, Greece
Registered: 2020-02-18
Posts: 430

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

Thanks you a lot, this really answers to my question.
So probably I should not try to deal with ClientRestoreCallbacks option

Offline

#12 2024-10-28 08:21:18

zen010101
Member
Registered: 2024-06-15
Posts: 70

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

Maybe AB has a easier solution with ClientRestoreCallbacks wink

Offline

#13 2024-10-28 18:48:31

dcoun
Member
From: Crete, Greece
Registered: 2020-02-18
Posts: 430

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

I think I managed to arrive to a working solution. Tcpview from microsoft was also real helpful to "close" connections
I have found also this thread: https://synopse.info/forum/viewtopic.php?id=5018
Useful will be to know how ClientRestoreCallbacks work and the automatic re-connection with full callbacks support mentioned here: https://synopse.info/forum/viewtopic.php?id=6066

Last edited by dcoun (2024-10-28 19:07:12)

Offline

#14 2024-10-29 03:27:46

zen010101
Member
Registered: 2024-06-15
Posts: 70

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

dcoun wrote:

I think I managed to arrive to a working solution.

Maybe you can share your solution so that everyone can be inspired. wink

Offline

#15 2024-11-10 09:11:18

dcoun
Member
From: Crete, Greece
Registered: 2020-02-18
Posts: 430

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

I was a bit late to answer because OnClientDisconnected caused the service to hang but with the https://github.com/synopse/mORMot2/issues/312 it works now a week without problems

In the client, I have assigned to TRestHttpClientWebsockets instance the OnWebSocketsUpgraded event before calling WebSocketsUpgrade
in the OnWebSocketsUpgraded event procedure I am getting the server's connection ID for that client: TRestHttpClientWebsockets(sender).WebSockets.WebSockets.ConnectionID and I pass with a call to the server at once together with a unique client ID that my client app has per client.

The server keeps a list of clients and connectionIDs in a IKeyValue directory that it gets from the above pathway and when OnClientDisconnected  in the server is fired, it searchs the IKeyValue to find if this connection ID exists.
if so, it pushs a "request" in a TSynThreadPool instance (mormot.core.threads) and exits. I could just remove it from a list but just it takes some time to do some more cleaning work, I do not want the server to wait. I keep also a timestamp to check using the unique client ID, if my client has already reconnected in the meantime, in order not to clear it. Probably the tdatetime should be converted to TTimeLog also.

This thread pool is created for asynchronous task by inheriting from it:

  TworkSoul=class
    public
      work:rawutf8; id:int64; dat:tdatetime;
  end;

TmySlavePool=class(TSynThreadPool)
    protected
      procedure Task(aCaller: TSynThreadPoolWorkThread; aContext: Pointer); override;
      procedure TaskAbort(aContext: Pointer); override;
  end;

var soulsbox:TmySlavePool;

procedure Tmyserver.WSclientDisconnects(Protocol: TWebSocketProtocol); 
var s:tworksoul;
begin 
s:=tworksoul.Create; 
s.work:='removeclient'; s.id:=protocol.ConnectionID; s.dat:=now; 
soulsbox.push(s); 
end;

Last edited by dcoun (2024-11-10 09:14:42)

Offline

#16 2024-11-10 09:35:24

zen010101
Member
Registered: 2024-06-15
Posts: 70

Re: ClientRestoreCallbacks in TWebSocketProcessSettings

If you try this commit: https://github.com/synopse/mORMot2/comm … c7a417d929, you will see that the server-side OnWSClose() can now be called regardless of whether the client is normally or abnormally disconnected, so that the TMyService.CallbackReleased() event inherited from TInterfacedObject or TInterfacedObjectRest will be called correctly.

Of course, there is no problem with your current approach, which allows for greater flexibility in handling disconnected events.

Offline

Board footer

Powered by FluxBB