doghappy / socket.io-client-csharp

socket.io-client implemention for .NET
MIT License
741 stars 129 forks source link

ConnectAsync appears to deadlock/not return #205

Closed grahambunce closed 3 years ago

grahambunce commented 3 years ago

I'm not really sure how to debug this to provide more information but:

We are trying to access a Socket.IO server - the usual URL we use from our node.js app is: https://[reacted]/socket.io/?franchise_id=ZZ&language_code=en&EIO=3&transport=websocket

i.e. we are fixing the server version to 3 and forcing websockets.

We need a C# client now so, using the following code:

        private readonly SocketIO _client = new SocketIO("https://[redacted], new SocketIOOptions()
        {
            ConnectionTimeout = new TimeSpan(0, 0, 5),
            Query = new List<KeyValuePair<string, string>>()
                {
                    new KeyValuePair<string, string>("franchise_id",FranchiseId.ToString()),
                    new KeyValuePair<string, string>("language_code","en"),
                },
            AutoUpgrade = true
        });

and

 _client.OnReconnectAttempt += (sender, e) =>
            {
                Debug.WriteLine($"reconnect attempt {e}");
            };

            _client.OnError += (sender, e) =>
            {
                Debug.WriteLine($"error {e}");
            };

            _client.OnConnected += async (sender, e) =>
            {
                Debug.WriteLine($"connected {e.ToString()}");

                await _client .EmitAsync("command", new
                {
                    type = "subscribe",
                    subscriptionType = SubscriptionType.All,
                    filter = SubscriptionFilter.None,
                    keys = new string[]
                    {
                    }
                });
            };

            _client.On("push", body =>
            {
                OnContentReceived(new ReceivedContentEventArgs()
                {
                    Body = body.ToString()
                });
            });

            _client.HttpClient = _hcf.CreateClient("default");

            await _client.ConnectAsync();

            var result = _client.Connected;

I can see from the HTTP Client logs I've injected the following:

info: System.Net.Http.HttpClient.default.LogicalHandler[100]
      Start processing HTTP request GET https://[redacted]/socket.io/?EIO=4&transport=polling&franchise_id=1&language_code=en
info: System.Net.Http.HttpClient.default.ClientHandler[100]
      Sending HTTP request GET https://[redacted]/socket.io/?EIO=4&transport=polling&franchise_id=1&language_code=en
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: System.Net.Http.HttpClient.default.ClientHandler[101]
      Received HTTP response headers after 437.9357ms - 200
info: System.Net.Http.HttpClient.default.LogicalHandler[101]
      End processing HTTP request after 466.2514ms - 200

So the call is being made, upgraded (I think) to WebSockets but then the " await _sdds.ConnectAsync();" code never exits and the OnConnected event never fires.

It's as if the code has deadlocked but I'm not sure what additional information I can provide to prove this.

if I add an additional query string "&transport=websockets" then this forces a 400 error (because the framework automatically adds a "&transport=polling" and we obviously can't have this twice) but then this invokes the "OnReconnected" event repeatedly.

doghappy commented 3 years ago

By default, the library will always try to connect to the server. OnReconnecting will be triggered when reconnecting; OnReconnectError will be triggered when reconnecting fails;

In your example, you registered the OnReconnectAttempt handler, is it triggered?

If it is triggered, you can set Reconncetion = false for options, and the library will throw an exception after the connection fails.

Otherwise, you need to use a debugging tool like Fiddler to do some debugging. If you can provide the server code and delete the irrelevant code, I can help you find the problem quickly.

grahambunce commented 3 years ago

@doghappy No, the OnReconnectAttempt handler isn't triggered - none of the events I've subscribed to are. The call is made to ConnectAsync and then the whole app appears to freeze (unless it's just waiting for something) and the ConnectAsync never returns.

By server code, do you mean the socket.IO server we have running in node.js? If so then I'm not sure I can look into that, it's managed by another team in the company who we do not have access to.

In fiddler I can see this:

Request:

GET https://[redacted]/socket.io/?EIO=4&transport=polling&franchise_id=1&language_code=en HTTP/1.1
Host: [redacted]
Accept-Encoding: gzip, deflate

Response:

HTTP/1.1 200 OK
Server: nginx/1.19.1
Date: Fri, 17 Sep 2021 07:41:24 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 104
Connection: keep-alive
Set-Cookie: sddswsroute=1631864485.143.787.550324; Expires=Sun, 19-Sep-21 07:41:24 GMT; Max-Age=172800; Path=/; HttpOnly
Access-Control-Allow-Origin: *
Set-Cookie: io=wOuAvDB9Jj6yE0VrAL8N; Path=/; HttpOnly

97:0{"sid":"wOuAvDB9Jj6yE0VrAL8N","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":30000}2:40

and then nothing else afterwards, even if I leave it several minutes.

This is running under a .NET 5.0 background worker process in a console app.

doghappy commented 3 years ago

I can be sure that your server uses socket.io v2.x.

If you don’t have permission to upgrade server socket.io, try using SocketIOClient v2.3.1, But it only supports ws, not http polling

        private readonly SocketIO _client = new SocketIO("https://[redacted], new SocketIOOptions()
        {
            Query = new Dictionary<string, string>()
                {
                    { "franchise_id",FranchiseId.ToString()},
                    {"language_code","en"},
                },
            EIO = 3
        });
grahambunce commented 3 years ago

@doghappy I've downloaded the latest code and am debugging through the client - I've replaced the sample code with my own to debug. This crashes - the code wasn't locking up after all - my exception handling failed to trap the error :(

I've also been told that my server was 2.2.0, so you are right, I need to use version 2.2.3 of the client.

In case it matters, the failing code is "TransportRouter", "ConnectAsync", line 80, i.e.

 public async Task ConnectAsync()
        {
            ...

            int index = text.IndexOf('{');
            string json = text.Substring(index);
            var info = System.Text.Json.JsonSerializer.Deserialize<HandshakeInfo>(json);

The response it's dealing with is

97:0{"sid":"2pGY5F4FBQDDeVyiALts","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":30000}2:40

This gets converted to

{"sid":"2pGY5F4FBQDDeVyiALts","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":30000}2:40

which then forces the JSON parser to fall over with:

'2' is invalid after a single JSON value. Expected end of data. LineNumber: 0 | BytePositionInLine: 96.

I can fix this by changing/adding:

            int index = text.IndexOf('{');
            int lastIndexOf = text.LastIndexOf("}");

            string json = text.Substring(index,(lastIndexOf - index) + 1);

This may not be necessary for a 3.x or 4.x server but just in case I wanted to record it

doghappy commented 3 years ago
97:0{"sid":"2pGY5F4FBQDDeVyiALts","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":30000}2:40

This text contains 2 socket.io messages

97: message length

so first message is:

image

0: open

The second message is

2:40

2: message length 40: connected


This message format means that you are using socket.io-server v2.x and use http polling to send/receive messages.

Although you have modified the code, you will encounter other problems.

I can be sure that your server uses socket.io v2.x.

If you don’t have permission to upgrade server socket.io, try using SocketIOClient v2.3.1, But it only supports ws, not http polling

        private readonly SocketIO _client = new SocketIO("https://[redacted], new SocketIOOptions()
        {
            Query = new Dictionary<string, string>()
                {
                    { "franchise_id",FranchiseId.ToString()},
                    {"language_code","en"},
                },
            EIO = 3
        });
doghappy commented 3 years ago

Essentially, socket.io-server v2 uses engin.io v3, and socket.io-server v3/v4 uses engin.io v4.

They are very different

grahambunce commented 3 years ago

thanks for the feedback. We will use the older version of the package

vikoms commented 3 years ago
97:0{"sid":"2pGY5F4FBQDDeVyiALts","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":30000}2:40

This text contains 2 socket.io messages

97: message length

so first message is:

image

0: open

The second message is

2:40

2: message length 40: connected

This message format means that you are using socket.io-server v2.x and use http polling to send/receive messages.

Although you have modified the code, you will encounter other problems.

I can be sure that your server uses socket.io v2.x. If you don’t have permission to upgrade server socket.io, try using SocketIOClient v2.3.1, But it only supports ws, not http polling

        private readonly SocketIO _client = new SocketIO("https://[redacted], new SocketIOOptions()
        {
            Query = new Dictionary<string, string>()
                {
                    { "franchise_id",FranchiseId.ToString()},
                    {"language_code","en"},
                },
            EIO = 3
        });

i have the same error, what should i do?? if i use version 2.3.1, socket doesn't have HTTPclient property, which is where i need to add HTTPClientHanlder,

I think you have converted the json response wrong, please let me know if there are any changes, thanks :)

doghappy commented 3 years ago

The latest version already supports socket.io server v2.x

grahambunce commented 3 years ago

@doghappy sorry, I’m confused. The latest package did not work with my socket io v2 server but the older package worked fine first time.

Are you saying that you think the latest package should work with a v2 server? Because it didn’t and, I think, the documentation suggests that the latest package only supports v3 and v4 servers

doghappy commented 3 years ago

Did you specify the EIO option? If your server is using socket.io server v2.x, please explicitly set it to 3

            var client = new SocketIO(connectionInfo.Uri, new SocketIOOptions
            {
                EIO = 3
            });

https://github.com/doghappy/socket.io-client-csharp#options

doghappy commented 3 years ago

By default, the client uses websocket to transmit data. If your server does not open websocket, you can also change the client's Transport option and set it to Polling