Socket.io 1.x: use WebSockets only?
Asked Answered
E

4

55

We are developing a web application that will run only on modern browsers (IE10+) for different reasons.

One of the features we implemented is Socket.io 1.x. However, by default the Socket.io client tries to support older browsers, so it starts a connection with long polling and then updates that to WebSockets. This is a waste of time and resources, given we know for sure the browser supports WS.

I've searched around, and I can only find this wiki page which, however, is about Socket.io 0.9.

Eventually, I found the documentation for engine.io-client (on which Socket.io-client is based on the 1.x branch). This is the code that I wrote and seems to be working. However, I would like to know if it's correct or if I'm doing something wrong:

io.connect('https://...', {
    upgrade: false,
    transports: ['websocket']
})

Weirdly, just setting the transports property to an array with websockets only wasn't enough; I also had to disable upgrade. Is this correct?

Update

I made some new discoveries.

With transports set to ['websocket'] only, it doesn't make any difference wether upgrade is enabled or not. Is that normal?

Eyrie answered 30/1, 2015 at 15:1 Comment(1)
Heads-up for Googlers: If you have the opposite problem (needing to use HTTP only), you can use io.set('transports', ['polling']);, or if you're on NestJS, @WebSocketGateway({ transports: ['polling'] }).Johanna
E
83

There are two types of "upgrades" happening with socket.io. First (in socket.io 1.0+), socket.io starts all connections with an http polling request and it may actually exchange some initial data with just an http request. Then, at some point after that, it will try to actually initiate a webSocket connection. the webSocket connection is done by sending a particular type of http request that specifies an upgrade: websocket header and the server can then respond appropriately whether it supports websocket or not. If the server agrees to the upgrade, then that particular http connection is "upgraded" to the webSocket protocol. At that point, the client then knows that webSocket is supported and it stops using the polling http requests, thus completing its upgrade to webSocket.

You can prevent the initial http polling entirely by doing this on the client:

var socket = io({transports: ['websocket'], upgrade: false});

This will prevent polling connections from your own cooperating clients. If you want to prevent any clients from ever using polling, then you can add this to the server:

io.set('transports', ['websocket']);

But, if you set this on the server, socket.io clients that are initially connecting with http polling will not work at all. So, this should only be matched with the right settings in the client such that the client never starts with polling.

This will tell both ends that you only want to use webSockets and socket.io will skip the extra http polling at the beginning. Fair warning, doing this requires webSocket support so this rules out compatible with older versions of IE that didn't yet support webSocket. If you want to retain compatibility, then just let socket.io do it's thing with a couple http requests initially.


Here's more info on the protocol upgrade from http to webSocket.

The webSockets protocol initiates EVERY webSocket with an HTTP connection. That's the way all webSockets work. That HTTP connection contains some headers on it that indicate that the browser would "like" to upgrade to the webSockets protocol. If the server support that protocol, then it responds telling the client that it will upgrade to the webSocket protocol and that very socket then switches from the HTTP protocol to the webSocket protocol. This is how a webSocket connection is designed to work. So, the fact that you see your webSocket connection starting with an HTTP connection is 100% normal.

You can configure socket.io to NEVER use long polling if that makes you feel better, but this will not change the fact that the webSocket connection will still start with an HTTP connection that is then upgraded to the webSocket protocol and it will not improve the efficiency of operation in modern browsers that support webSockets. It will, however make it so that your connection will not work in older browsers.

Elayne answered 30/1, 2015 at 16:55 Comment(10)
Thanks for explaining. So, what does disabling upgrade do? By disabling it I see on the Chrome inspector that a GET request is made to wss://.../socket.io/?EIO=3&transport=websocket and the response status code is 101 (Switching protocols). That request has the status of "pending" always, and I can see the frames that the client and server exchange.Eyrie
Actually, it's even more weird. By setting the transports to websocket only, it doesn't make any difference on the Inspector wether upgrade is true or false. In both cases I only see one request, the one above (which is always a HTTP request, true, but made on the wss:// protocol)Eyrie
If I enable "polling", on the other hand, with upgrade true I can first see a few requests, and then the WSS one. With upgrade false, the websocket is never started.Eyrie
@Qualcuno - Since all webSocket connections start as an HTTP request and are then "upgraded" to the webSocket protocol, it wouldn't surprise me if turning the upgrade option off makes it so that it never turns into a webSocket connection. To confirm that, one would have to study the engine.io source code as the documentation does not really explain. I don't know exactly without further study of the source code. This is a good reference if you want to see how a webSocket connection is started.Elayne
@Qualcuno You are correct in your observations. The "upgrade" in Socket.IO doesn't refer to the HTTP to WSS protocol upgrade as is often misunderstood but to the upgrade of Socket.IO connection from long-polling AJAX connection to WebSocket. If you start with WebSocket already (which is not the default) than upgrade false has no effect because you don't need to upgrade. If you start with polling and disable upgrade then it stays that way and doesn't upgrade to WebSocket. See my answer for detailsOfficialese
Added an explanation about both types of upgrades or switches that are happening; 1-switching from long polling to webSocket 2-upgrade process from http to webSocket that initiates an actual webSocket connectionElayne
how to set this using socket.io java client, any helpIndigence
@IftikarUrrhmanKhan - What Java client are you using? Please provide a link to the actual module/documentation you are using.Elayne
@Elayne i am using github.com/socketio/socket.io-client-javaIndigence
@IftikarUrrhmanKhan - On this doc page, search for the first occurrence of transports.Elayne
S
36

To tell Socket.IO to use WebSocket only instead of a few XHR requests first, just add this to the Node server:

io.set('transports', ['websocket']);

And on the client add this:

var socket = io({transports: ['websocket']});

This tells Socket.IO to only use WebSocket protocol and nothing else; it's cleaner, faster and uses a little less resources on the client and server sides.

Now you'll only see a single WebSocket connection in your network request list, just keep in mind IE9 and earlier can't use WebSocket.

Stodder answered 26/12, 2015 at 1:32 Comment(3)
Do you suggest this to use ?Overseer
I personally use this, yes, and I suggest anyone who doesn't care about IE9 or older browsers to use this as well, i.e. if you are living in modern times, in 2017, yes, you should avoid XHR fallback and only use websocket transport. IE9 = < 0.01% of browsers, if you support them, keep in mind they will use your product ;)Stodder
Yeah I have implemented this on my site. Works awesome. I was wondering on console why there were many requests for socketio, your solution fixed it. And yes, I dont care about 0.01% :) since I never knew anybody who still uses IE, except my grandpa.Overseer
O
29

I'm posting that answer because the accepted answer is not correct - it confuses the Socket.IO upgrade from long-polling AJAX to WebSocket with the WSS protocol "Connection: Upgrade" request. The issue is not that the WebSocket connection starts as HTTP and gets upgraded to WebSocket - how could it not? - but that Socket.IO starts with a long-polling AJAX connection even on browsers supporting WebSocket, and only upgrades it later after exchanging some traffic. It's very easy to see in the developer tools of Firefox or Chrome.

The author of the question is correct in his observations. The "upgrade" in Socket.IO doesn't refer to the HTTP to WSS protocol upgrade as is often misunderstood but to the upgrade of Socket.IO connection from long-polling AJAX connection to WebSocket. If you start with WebSocket already (which is not the default) then upgrade false has no effect because you don't need to upgrade. If you start with polling and disable upgrade then it stays that way and doesn't upgrade to WebSocket.

See answers by arnold and Nick Steele if you want to avoid starting with long-polling. I will explain what is going on in more detail.

This is what I observed in my experiments with simple WebSocket and Socket.IO apps:

WebSocket

2 requests, 1.50 KB, 0.05 s

From those 2 requests:

  1. HTML page itself
  2. connection upgrade to WebSocket

(The connection upgrade request is visible on the developer tools with a 101 Switching Protocols response.)

Socket.IO

6 requests, 181.56 KB, 0.25 s

From those 6 requests:

  1. the HTML page itself
  2. Socket.IO's JavaScript (180 kilobytes)
  3. first long polling AJAX request
  4. second long polling AJAX request
  5. third long polling AJAX request
  6. connection upgrade to WebSocket

Details

WebSocket results that I got on localhost:

WebSocket results - websocket-vs-socket.io module

Socket.IO results that I got on localhost:

Socket.IO results - websocket-vs-socket.io module

Test yourself

I published the code on npm and on GitHub, you can run it yourself:

# Install:
npm i -g websocket-vs-socket.io
# Run the server:
websocket-vs-socket.io

and follow instrictions. To uninstall:

# Uninstall:
npm rm -g websocket-vs-socket.io

See this answer for more info.

Officialese answered 25/7, 2016 at 3:8 Comment(1)
Nice work gathering all that data in one place! Thanks! If all work was as thorough as this life would come with a manual :)Stodder
C
11

I thought I should add to the accepted answer above, as if anyone wants to eliminate the XHR Polling transport and initiate websockets right away. The code below is just to give an idea of the implementation:

var url = serverUrl + "/ssClients"  //ssClients is the socket.io namespace

var connectionOptions =  {
    "force new connection" : true,
    "reconnection": true,
    "reconnectionDelay": 2000,                  //starts with 2 secs delay, then 4, 6, 8, until 60 where it stays forever until it reconnects
    "reconnectionDelayMax" : 60000,             //1 minute maximum delay between connections
    "reconnectionAttempts": "Infinity",         //to prevent dead clients, having the user to having to manually reconnect after a server restart.
    "timeout" : 10000,                           //before connect_error and connect_timeout are emitted.
    "transports" : ["websocket"]                //forces the transport to be only websocket. Server needs to be setup as well/
}
var socket = require("socket.io-client")(url, connectionOptions); 

socket.on("connect", function (_socket) {
    logger.info("Client connected to server: " + clientName);
    logger.info("Transport being used: " + socket.io.engine.transport.name);

    socket.emit("join", clientName, function(_socketId) {  //tell the server the client name
        logger.info("Client received acknowledgement from server: " + _socketId);
        logger.info("Transport being used after acknowledgement: " + socket.io.engine.transport.name);

    });
});

After the server is setup, you will see this:

2015-10-23T19:04:30.076Z - info:    Client connected to server: someClientId 
2015-10-23T19:04:30.077Z - info:    Transport being used: websocket 
2015-10-23T19:04:30.081Z - info:    Client received acknowledgement from server: aMH0SmW8CbiL8w5RAAAA
2015-10-23T19:04:30.081Z - info:    Transport being used after acknowledgement: websocket

If you don't force the transport, you'd see "polling" instead of websocket. However, this doesn't happen on the client side alone, the server must be setup as well:

var io = require("socket.io")(server, { adapter: adapter, log: false }); //attach io to existing Express (http) server
..
io.set('transports', ['websocket']); //forces client to connect as websockets. If client tries xhr polling, it won't connect.

Danger

If the client actually does not support the websocket protocol, a connection won't happen and the client will report an xhr poll error.

This is working perfectly for me because I can control the clients I have, so I have the luxury to force websockets right away, which I believe is what the original question is asking. I hope this helps someone out there...

Chappy answered 23/10, 2015 at 19:54 Comment(3)
what is the point/use of disabling XHR polling on the server if it is already disabled on the client?Malcah
Excellent work ! Thx. In my case i refer to https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js therefore to be able to connect to my websocket i just used var url = 'http://localhost:8080'; var socket = io.connect(url,connectionOptions); and got my Status 101 =)Beanfeast
Hi if I am using the Node.js Express Clustering module on the server then will this work...?Eliseo

© 2022 - 2025 — McMap. All rights reserved.