Jump to content
Registration disabled at the moment Read more... ×
djhfwk

TSslWebSocketCli: Incorrect sequence of OnConnected and OnFrameRcvd events

Recommended Posts

In the current version (V9.4) of TSslWebSocketCli, the OnFrameRcvd event is triggered before the OnConnected event.

 

This is the full set of modifications I made for OverbyteIcsWebSocketCli.pas, and it requires review.

 

I added a new value to TWebSocketState: wssConnected. This state represents the situation where the server has already responded with "101 Switching Protocols", but the internal HttpState has not yet reached httpReady. In TSslWebSocketCli.IsWSConnected, HttpState need to be httpReady.

 

I overrode StateChange to wait for this condition. I’m curious why the httpReady state is always slightly delayed. With this change, OnConnected is now triggered earlier, before OnFrameRcvd, which aligns better with the expected WebSocket event sequence.

 

However, there is still one issue: OnFrameRcvd may be triggered before WSConnect returns. I'm not sure whether this behavior is acceptable or expected. Please advise.

 

Using Demo:  OverbyteIcsSnippets  as client and OverbyteIcsSslMultiWebServ as server

 

TWebSocketState = (wssHttp, wssConnecting, wssConnected, wssReady);

function TSslWebSocketCli.WSConnect: Boolean;
begin
    Result := False;
    RequestVer := '1.1';
    HeaderUpgrade := '';
    HeaderConnection := '';
    HeaderSecWebSocketAccept := '';
    HeaderSecWebSocketProtocol := '';
    HeaderAccessControlAllowOrigin := '';
    FWSFrameCounter := 0;
    if (DebugLevel >= DebugConn) then
        LogEvent('WebSocket: Connecting to: ' + URL);
//    WSState := wssConnecting;  { in case server returns data quickly }

    Get;  // sync request

    Result:= WSState = wssReady;
end;

procedure TSslWebSocketCli.StateChange(NewState: THttpState);
begin
    inherited;
    if (NewState = httpReady) and (WSState = wssConnected) then
    begin
        WSState := wssReady;
        if Assigned(FOnWSConnected) then
            FOnWSConnected(Self);
    end;
end;

procedure TSslWebSocketCli.SocketDataAvailable(Sender: TObject; ErrCode: Word);
var
    Len: Integer;
{ JK 28.11.2023 }
    BufPos : Integer;
    ParsedBytes : Integer;
{ JK 28.11.2023 end }
    ServerKey : String;
    s : AnsiString;
    KeyComp: Boolean;
begin
    if (NOT (State = httpReady)) OR (WSState <> wssReady) then begin
        inherited SocketDataAvailable(Sender, ErrCode);
     //   if FReceiveLen >= 2 then
     //       LogEvent('WebSocket: Inherited Recv Raw: ' + IcsTBytesToString(FReceiveBuffer, FReceiveLen));  // !!! TEMP
        if (WSState <> wssConnecting) then
            Exit;

        //The protocol has been upgraded from HTTP to WebSocket.
        //This is when the connection is truly considered established.

        if (StatusCode = 101) then begin
            if SameText(HeaderUpgrade, 'websocket') and SameText(HeaderConnection, 'Upgrade') then begin
                s := AnsiString(ClientKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11');
                ServerKey := String(IcsBase64EncodeA(SHA1ofStr(s)));          { V9.1 corrected string cast, V9.4 }
                KeyComp := (HeaderSecWebSocketAccept = ServerKey);
                if (NOT KeyComp) and (DebugLevel >= DebugConn) then
                    LogEvent('WebSocket: Server Key Comparison Failed');
            end
            else if (DebugLevel >= DebugConn) then
                LogEvent('WebSocket: Failed to Upgrade to WebSocket Protocol');
            end
            else if (DebugLevel >= DebugConn) then
                LogEvent('WebSocket: Failed to Connect: ' + LastResponse);
            if KeyComp then begin
                if (DebugLevel >= DebugConn) then
                    LogEvent('WebSocket: Connected OK');
                //change the state to wssConnected, then wait httpReady
                WSState := wssConnected;  { in case server returns data quickly }
                LastReceivedDataTickCount := IcsGetTickCount64;
                LastSentPingTickCount := 0;
                if (FWSPingSecs > 0) and (FWSPingSecs < 5) then
                    FWSPingSecs := 5;
                FPeriodicTimer.Enabled := true;
            end
        else
            WSState := wssHttp;
    end;
........

 

OverbyteIcsWebSocketCli.pas

Edited by djhfwk

Share this post


Link to post

I'll look at your changes, but I rewrote the WSConnect function yesterday, adding an async option so it is no longer blocking, which was a serious anomaly for ICS.  

 

I've also changed the ICS server component not to send welcome or other data immediately, before the client has a chance to process the 101 command and switch to Websocket mode.  

 

Still testing all this. 

 

Angus

 

Share this post


Link to post

 

41 minutes ago, Angus Robertson said:

I'll look at your changes, but I rewrote the WSConnect function yesterday, adding an async option so it is no longer blocking, which was a serious anomaly for ICS.  

 

I've also changed the ICS server component not to send welcome or other data immediately, before the client has a chance to process the 101 command and switch to Websocket mode.  

 

Still testing all this. 

 

Angus

 

This is indeed great news — hats off to the hardworking author of ICS!

Edited by djhfwk

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×