Skip to content
This repository has been archived by the owner on Jan 29, 2023. It is now read-only.

Run websocket only mode for Socket.IO #16

Closed
iKK001 opened this issue Dec 6, 2021 · 8 comments
Closed

Run websocket only mode for Socket.IO #16

iKK001 opened this issue Dec 6, 2021 · 8 comments
Labels
enhancement New feature or request

Comments

@iKK001
Copy link

iKK001 commented Dec 6, 2021

Is there a way to run "websocket" only mode with Socket.IO on this Arduino library ?

I made a local Socket.IO client that works well with the WebSockets_Generic Example "WiFiNINA-->WebSocketClientSocketIO_NINA" - but only if the server has "transport=["polling", "websocket"]," defined.

If I change the server to "transport=["websocket"]," then it does not work anymore.

However, our application absolutely requires "websocket only" configuration !

On other Socket.IO clients there is a possibility to force to "websocket only".
For example in the iOS-client there is a config setting called ".forceWebsockets(true)," that makes exactly that.

The keyword, I think, is called "websocket only". Please refer to the Socket.IO documentation here and the error you get if websocket only is missing is explained here

Is there a "websocket only" mode for the WebSockets_Generic library ?
If yes, how would I configure it ?

@khoih-prog
Copy link
Owner

I'm sorry I don't understand the reason and won't spend time to deal with your unusual use-case, even if this is technically feasible.

Please read Socket.io room-support

Note: Socket.IO is not a WebSocket implementation. Although Socket.IO indeed uses WebSocket as a transport when possible, it adds some metadata to each packet: the packet type, the namespace and the ack id when a message acknowledgement is needed. That is why a WebSocket client will not be able to successfully connect to a Socket.IO server, and a Socket.IO client will not be able to connect to a WebSocket server

Good Luck,

@khoih-prog khoih-prog added the wontfix This will not be worked on label Dec 10, 2021
@iKK001
Copy link
Author

iKK001 commented Dec 10, 2021

I completely understand that Socket.IO is not a Websocket implementation.

However, your library supports Socket.IO (at least with one concrete example in your library) - therefore I was hoping that you support entire Socket.IO capability (including "websocket only" mode).

If you look closer to the Socket.IO documentation, Socket.IO is capable to connect in a mode called "websocket only" (as opposed to "polling").

And this "websocket only" mode of Socket.IO is obviously badly supported/documented with your Arduino library.

The reasons we want the "websocket only" mode are:
--> being able to get a speedy connectivity without the need of a load-balancer
(our application is hosted on Google Cloud Run and we use Socket.IO to exchange fast messages. We have iOS, Android and Web running with this "websocket only" mode - no issues. And we would like to achieve the same now with Arduino.)

Due to scallablility and the limited number of clients of one Socket.IO node, the Cloud Run Server creates new nodes whenever needed. And those nodes are slow if used with "polling". Therefore the need of a "websocket only" mode.
If Arduino can't, we are oblidged to use an expensive load balancer. And we would not like that really.

@khoih-prog
Copy link
Owner

I'll create a new version for you to test, using transport=websocket instead of transport=polling.

Will let you know when it's ready by tomorrow

khoih-prog added a commit that referenced this issue Dec 12, 2021
### Release v2.11.1

1. Add option to use `transport=websocket` with sticky-session SIO server. Check [Run websocket only mode for Socket.IO #16](#16)
2. Add some Sticky_SIO-related examples
khoih-prog added a commit that referenced this issue Dec 12, 2021
### Release v2.11.1

1. Add option to use `transport=websocket` with sticky-session SIO server. Check [Run websocket only mode for Socket.IO #16](#16)
2. Add some Sticky_SIO-related examples
khoih-prog added a commit that referenced this issue Dec 12, 2021
### Release v2.11.1

1. Add option to use `transport=websocket` with sticky-session SIO server. Check [Run websocket only mode for Socket.IO #16](#16)
2. Add some Sticky_SIO-related examples
@khoih-prog
Copy link
Owner

khoih-prog commented Dec 12, 2021

Hi @iKK001

The WebSockets_Generic Releases v2.11.1 has just been published to to add support to websocket only mode for Socket.IO (sticky-session mode)

Your enhancement request, leading to new v2.11.1, has also been noted in Contributions and Thanks.

You have to use WebSocketClient_Sticky_SocketIO_NINA instead of WebSocketClientSocketIO_NINA, with the new selection

// For transport=websocket instead of transport=polling
#define USING_STICKY_SESSION_SIO        true

See the new handshake now is Handshake:GET /socket.io/?EIO=4&transport=websocket HTTP/1.1

Start Portenta_H7_WebSocketClient_Sticky_SocketIO on PORTENTA_H7_M7 with Ethernet using Portenta_Ethernet Library
WebSockets_Generic v2.11.1
WebSockets Client @ IP address: 192.168.2.133
Connecting to WebSockets Server @ IP address: 192.168.2.30, port: 8080
[WS] WebSockets_Generic v2.11.1
[WS] [wsIOc] found EIO=4 disable EIO ping on client
[WS] [WS-Client][connectedCb] Connected to Host:192.168.2.30, Port:8080
[WS] [WS-Client] [sendHeader] Sending header...
[WS] sendHeader: client->cKey = Kp/b0RFGMw/WSNGn35/s3A==
[WS] [WS-Client] [sendHeader] Handshake:GET /socket.io/?EIO=4&transport=websocket HTTP/1.1    <===== new mode
Host: 192.168.2.30:8080
Connection: keep-alive
Authorization: 1234567890
User-Agent: arduino-WebSocket-Client

Please test and post any new issue of this new release. Thanks.

Best Regards,


Release v2.11.1

  1. Add option to use transport=websocket with sticky-session SIO server. Check Run websocket only mode for Socket.IO #16
  2. Add some Sticky_SIO-related examples

@khoih-prog khoih-prog added enhancement New feature or request and removed wontfix This will not be worked on labels Dec 12, 2021
@iKK001
Copy link
Author

iKK001 commented Dec 13, 2021

Thank you very much for this kind extension of the library. I appreciate it.

Using your new "sticky-session mode" library update, I observed that the initial connection starts with a "https" connection.

However, Socket.IO in a "sticky-session only mode" must work with "wss" protocol.

  • with "transports: ["polling"]" , you can see that the request-protocol is http.
  • with "transports: ["polling", "websocket"]", you can see that the initial request is also "http" - then an upgrade happens, and thereafter "ws://" is used (since the protocols change)
  • with "transports: ["websocket"], and the USING_STICKY_SESSION_SIO = true, you can see that the initial request is again "http" --> which is wrong! The initial request should already be "ws://" protocol.

I suscpect that you tested the Arduino library change on a http-server instead of a socket.io-server.

Below a working example established on a Web-client (not Arduino) with just "websocket only transport".
As you can see in the Request-Example shows clearly only "ws" (instead of "http").

Working Example (Web-client):
Screenshot 2021-12-12 at 15 33 26

@khoih-prog
Copy link
Owner

Because you didn't specify how your server is using, the code WebSocketClient_Sticky_SocketIO_NINA example was tested with this Multi_Sticky_SIO server as follows:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
const process = require('process');

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8080);

  console.log(`Worker ${process.pid} started on port 8080`);
}

If you're using different server mode, such as specified in Using multiple nodes with nodejs-cluster, you can just use the normal WebSocketClientSocketIO_NINA example

socket.io sticky-session Server

const cluster = require("cluster");
const http = require("http");
const { Server } = require("socket.io");
const numCPUs = require("os").cpus().length;
const { setupMaster, setupWorker } = require("@socket.io/sticky");
const { createAdapter, setupPrimary } = require("@socket.io/cluster-adapter");

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  const httpServer = http.createServer();

  // setup sticky sessions
  setupMaster(httpServer, {
    loadBalancingMethod: "least-connection",
  });

  // setup connections between the workers
  setupPrimary();

  // needed for packets containing buffers (you can ignore it if you only send plaintext objects)
  // Node.js < 16.0.0
  cluster.setupMaster({
    serialization: "advanced",
  });
  // Node.js > 16.0.0
  // cluster.setupPrimary({
  //   serialization: "advanced",
  // });

  //httpServer.listen(3000);
  httpServer.listen(8080);

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on("exit", (worker) => {
    console.log(`Worker ${worker.process.pid} died`);
    cluster.fork();
  });
} else {
  console.log(`Worker ${process.pid} started on 8080 port`);

  const httpServer = http.createServer();
  const io = new Server(httpServer);

  // use the cluster adapter
  io.adapter(createAdapter());

  // setup connection with the primary process
  setupWorker(io);

  io.on("connection", (socket) => {
    /* ... */
  });
}

I think the transport mode will be automatically changed to during the negotiation from

Handshake:GET /socket.io/?EIO=4&transport=polling HTTP/1.1

to

Handshake:GET /socket.io/?EIO=4&transport=websocket&sid=AZdu9h8dZ3ZOqgiaAAAA HTTP/1.1

Debug Terminal

Start ESP8266_WebSocketClientSocketIO on ESP8266_NODEMCU_ESP12E
WebSockets_Generic v2.11.1

WebSockets Client started @ IP address: 192.168.2.132
Connecting to WebSockets Server @ IP address: 192.168.2.30, port: 8080
[WS] [WS-Client] beginSocketIO with IPAddress
[WS] [WS-Client] beginSocketIO with const char
[WS] WebSockets_Generic v2.11.1
[WS] [wsIOc] found EIO=4 disable EIO ping on client
[WS] [WS-Client] Connect ws...
[WS] [WS-Client] connectedCb
[WS] [WS-Client][connectedCb] Connected to Host:192.168.2.30, Port:8080
[WS] [WS-Client] [sendHeader] Sending header...
[WS] sendHeader: client->cKey = Rw3kPWMBT0VXNqhWW0y/xA==
[WS] [WS-Client] [sendHeader] Handshake:GET /socket.io/?EIO=4&transport=polling HTTP/1.1  <===  orig. Handshake 
Host: 192.168.2.30:8080
Connection: keep-alive
Authorization: 1234567890
User-Agent: arduino-WebSocket-Client


[WS] [write] n:166, t:10104
[WS] [write] Write, Length :166, Left :0
[WS] [WS-Client] [sendHeader] Sending header... Done (us):29229
[WS] [WS-Client][handleHeader] RX:HTTP/1.1 200 OK
[WS] [WS-Client][handleHeader] RX:Content-Type: text/plain; charset=UTF-8
[WS] [WS-Client][handleHeader] RX:Content-Length: 97
[WS] [WS-Client][handleHeader] RX:Date: Tue, 14 Dec 2021 00:51:13 GMT
[WS] [WS-Client][handleHeader] RX:Connection: keep-alive
[WS] [WS-Client][handleHeader] RX:Keep-Alive: timeout=5
[WS] [WS-Client][handleHeader] Header read fin.
[WS] [WS-Client][handleHeader] Client settings:
[WS] [WS-Client][handleHeader] - cURL:/socket.io/?EIO=4
[WS] [WS-Client][handleHeader] - cKey:Rw3kPWMBT0VXNqhWW0y/xA==
[WS] [WS-Client][handleHeader] Server header:
[WS] [WS-Client][handleHeader] - cCode:200
[WS] [WS-Client][handleHeader] - cIsUpgrade:0
[WS] [WS-Client][handleHeader] - cIsWebsocket:1
[WS] [WS-Client][handleHeader] - cAccept:
[WS] [WS-Client][handleHeader] - cProtocol:arduino
[WS] [WS-Client][handleHeader] - cExtensions:
[WS] [WS-Client][handleHeader] - cVersion:0
[WS] [WS-Client][handleHeader] - cSessionId:
[WS] [WS-Client][handleHeader] Still missing cSessionId try Socket.IO
[WS] [WS-Client][handleHeader] socket.io json: 0{"sid":"AZdu9h8dZ3ZOqgiaAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":20000}
[WS] [WS-Client][handleHeader] - cSessionId: AZdu9h8dZ3ZOqgiaAAAA
[WS] [WS-Client][handleHeader] Header read fin.
[WS] [WS-Client][handleHeader] Client settings:
[WS] [WS-Client][handleHeader] - cURL:/socket.io/?EIO=4
[WS] [WS-Client][handleHeader] - cKey:Rw3kPWMBT0VXNqhWW0y/xA==
[WS] [WS-Client][handleHeader] Server header:
[WS] [WS-Client][handleHeader] - cCode:200
[WS] [WS-Client][handleHeader] - cIsUpgrade:0
[WS] [WS-Client][handleHeader] - cIsWebsocket:1
[WS] [WS-Client][handleHeader] - cAccept:
[WS] [WS-Client][handleHeader] - cProtocol:arduino
[WS] [WS-Client][handleHeader] - cExtensions:
[WS] [WS-Client][handleHeader] - cVersion:0
[WS] [WS-Client][handleHeader] - cSessionId:AZdu9h8dZ3ZOqgiaAAAA
[WS] [WS-Client][handleHeader] found cSessionId
[WS] [WS-Client] [sendHeader] Sending header...
[WS] sendHeader: client->cKey = /TMYxAAoTpVCfzn3Ic/zsA==
[WS] [WS-Client] [sendHeader] Handshake:GET /socket.io/?EIO=4&transport=websocket&sid=AZdu9h8dZ3ZOqgiaAAAA HTTP/1.1   <==============  Handshake updated here
Host: 192.168.2.30:8080
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: /TMYxAAoTpVCfzn3Ic/zsA==
Sec-WebSocket-Protocol: arduino
Authorization: 1234567890
User-Agent: arduino-WebSocket-Client

I'm sorry I don't have more time to help here.

I suggest if you have more issues, test on ESP32/ESP8266, then post the issue in arduinoWebSockets library issues for help.

Good Luck,

@iKK001
Copy link
Author

iKK001 commented Dec 14, 2021

I understand that it was not clear how I set up the server.

I also understand that it takes a bit of a moment to understand the two major ways you can run a multi-node Socket.IO configuration. In my case, there are several server nodes running but without "sticky-session" and without http-protocol.

The way I run the multiple Server-nodes is without the need of an expensive load-balancer. (i.e. only with "websocket" and NO long-polling http). (as described here under Remarks point 2) - it says:

the WebSocket transport does not have this limitation, since it relies on a single TCP connection for the whole session. Which means that if you disable the HTTP long-polling transport (which is a perfectly valid choice in 2021), you won't need sticky sessions:

Therefore I am not running the server-nodes with "sticky sessions" but instead with "websocket only".

You can achieve that by instantiating one server-node with the necessary Server-Option as follows:

const io = new Server(httpServer, {
    transports: ["websocket"],
});

(Instead of what you did: const io = new Server(httpServer);)

The corresponding Web-Client option is described here.

So it is very simple for you to test:

Simply add the Server-Option transports: ["websocekt"], to your server and you are good to go !

All we need is a server that only relies on "websocket" (and no longer uses any kind of http).

With such a "websocket-only"-Server, you can then hopefully see what to improve in your client-library. The important thing is that your library only uses "wss://" protocol (or "ws://" for local server) at any time (i.e. no longer "http" and upgrade to "ws" as in default mode).

Please give it a chance. Thank you very much.

@khoih-prog
Copy link
Owner

That websocket-only-Server is not suggested, per Socket.IO Upgrade mechanism section

By default, the client establishes the connection with the HTTP long-polling transport.

But, why?

While WebSocket is clearly the best way to establish a bidirectional communication, experience has shown that it is not always possible to establish a WebSocket connection, due to corporate proxies, personal firewall, antivirus software...

From the user perspective, an unsuccessful WebSocket connection can translate in up to at least 10 seconds of waiting for the realtime application to begin exchanging data. This perceptively hurts user experience.

To summarize, Engine.IO focuses on reliability and user experience first, marginal potential UX improvements and increased server performance second.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants