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?
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.
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.
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:
HTML page itself
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:
the HTML page itself
Socket.IO's JavaScript (180 kilobytes)
first long polling AJAX request
second long polling AJAX request
third long polling AJAX request
connection upgrade to WebSocket
Details
WebSocket results that I got on localhost:
Socket.IO results that I got on localhost:
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.
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...
Related
I have a nodejs express Rest API and I want to add websocket support.
I'm using this boilerplate for the api part and added this library and socket.io to use websockets in the same project (I'm not sure if it matters).
My websockets work as expected without the Rest API but as soon as I make an http request to my API, connections to the websocket fail with the following error:
WebSocket connection to 'ws://localhost:3000/socket.io/?EIO=3&transport=websocket' failed: Invalid frame header
Already established connections still work though.
This problem remains until I restart my server.
I found a lot of different possible causes for this error but none of which really helped me.
Edit:
This is how I connect to the websockets.
const rooms = io.connect(
'http://localhost:3000/rooms',
{ transports: ['websocket'] }
);
I solved this problem by switching my stack to nestjs.
The problem was probably because both websocket and rest api were running on the same port
I have a web application that uses WebSockets to communicate between browser and server. When serving as ws, everything works as intended. If I change the protocol to wss, things mostly work as expected (the majority of messages passed from client to server, or vice versa, are received), but I occasionally one of the following errors in the Chrome console:
"Could not decode a text frame as UTF-8."
or
"Invalid frame header"
...at which point Chrome releases the connection.
I have observed this both when serving wss directly from the server (runs on .NET, uses SuperWebSocket), and in a configuration where the server uses ws and Apache's mod_proxy_wstunnel to reverse proxy to this using the wss protocol. I have also set up a simple "echo" server under the same Apache configuration, and don't observe the issue; this leads me to believe there's something funny about the data we're passing to the SuperWebSocket API. (The messages which cause the error are valid UTF-8, and again, don't see this issue when serving over ws.)
I'm at a loss for how a protocol change would cause such an issue to occur, which leads me to my question:
Are there cases where a WebSocket frame might be valid when sent without TLS but would become corrupted when sent with TLS?
Are there cases where a WebSocket frame might be valid when sent without TLS but would become corrupted when sent with TLS?
No, wss:// is the same as ws:// only that it is not using plain TCP but TCP+TLS. The WebSockets protocol itself is not aware if it is running inside a plain TCP connection or a TLS protected TCP connection. This is similar to https:// vs. http://.
But a TLS connection is more sensible to data corruption. That is if some man in the middle modifies the packets properly simple ws:// will not notice while wss:// will croak because the modification of the packet was detected. But you should get the error then at the connection level (i.e. connection broke or similar) and not at the WebSockets level like in your case (invalid frame header).
I have no idea what you are running as a WebSockets backend but I would suggest that the problem lies there. Because of the additional TLS layer wss:// might behave slightly different regarding timing and buffering of data inside the server so there might be a race conditions which happens more likely when wss:// is in use compared to ws://.
How Websockets are implemented?
What is the algorithm behind this new tech (in comparison to Long-Polling)?
How can they be better than Long-Polling in term of performance?
I am asking these questions because here we have a sample code of Jetty websocket implementation (server-side).
If we wait long enough, a timeout will occur, resulting in the
following message on the client.
And that is definately the problem I'm facing when using Long-polling. It stops the process to prevent server overload, doesn't it ?
How Websockets are implemented?
webSockets are implemented as follows:
Client makes HTTP request to server with "upgrade" header on the request
If server agrees to the upgrade, then client and server exchange some security credentials and the protocol on the existing TCP socket is switched from HTTP to webSocket.
There is now a lasting open TCP socket connecting client and server.
Either side can send data on this open socket at any time.
All data must be sent in a very specific webSocket packet format.
Because the socket is kept open as long as both sides agree, this gives the server a channel to "push" information to the client whenever there is something new to send. This is generally much more efficient than using client-driven Ajax calls where the client has to regularly poll for new information. And, if the client needs to send lots of messages to the server (perhaps something like a mnulti-player game), then using an already open socket to send a quick message to the server is also more efficient than an Ajax call.
Because of the way webSockets are initiated (starting with an HTTP request and then repurposing that socket), they are 100% compatible with existing web infrastructure and can even run on the same port as your existing web requests (e.g. port 80 or 443). This makes cross-origin security simpler and keeps anyone on either client or server side infrastructure from having to modify any infrastructure to support webSocket connections.
What is the algorithm behind this new tech (in comparison to
Long-Polling)?
There's a very good summary of how the webSocket connection algorithm and webSocket data format works here in this article: Writing WebSocket Servers.
How can they be better than Long-Polling in term of performance?
By its very nature, long-polling is a bit of a hack. It was invented because there was no better alternative for server-initiated data sent to the client. Here are the steps:
The client makes an http request for new data from the client.
If the server has some new data, it returns that data immediately and then the client makes another http request asking for more data. If the server doesn't have new data, then it just hangs onto the connection for awhile without providing a response, leaving the request pending (the socket is open, the client is waiting for a response).
If, at any time while the request is still pending, the server gets some data, then it forms that data into a response and returns a response for the pending request.
If no data comes in for awhile, then eventually the request will timeout. At that point, the client will realize that no new data was returned and it will start a new request.
Rinse, lather, repeat. Each piece of data returned or each timeout of a pending request is then followed by another ajax request from the client.
So, while a webSocket uses one long-lived socket over which either client or server can send data to the other, the long-polling consists of the client asking the server "do you have any more data for me?" over and over and over, each with a new http request.
Long polling works when done right, it's just not as efficient on the server infrastructure, bandwidth usage, mobile battery life, etc...
What I want is explanation about this: the fact Websockets keep an
open connection between C/S isn't quite the same to Long Polling wait
process? In other words, why Websockets don't overload the server?
Maintaining an open webSocket connection between client and server is a very inexpensive thing for the server to do (it's just a TCP socket). An inactive, but open TCP socket takes no server CPU and only a very small amount of memory to keep track of the socket. Properly configured servers can hold hundreds of thousands of open sockets at a time.
On the other hand a client doing long-polling, even one for which there is no new information to be sent to it, will have to regularly re-establish its connection. Each time it re-establishes a new connection, there's a TCP socket teardown and new connection and then an incoming HTTP request to handle.
Here are some useful references on the topic of scaling:
600k concurrent websocket connections on AWS using Node.js
Node.js w/1M concurrent connections!
HTML5 WebSocket: A Quantum Leap in Scalability for the Web
Do HTML WebSockets maintain an open connection for each client? Does this scale?
Very good explanation about web sockets, long polling and other approaches:
In what situations would AJAX long/short polling be preferred over HTML5 WebSockets?
Long poll - request → wait → response. Creates connection to server like AJAX does, but keep-alive connection open for some time (not long though), during connection open client can receive data from server. Client have to reconnect periodically after connection is closed due to timeouts or data eof. On server side it is still treated like HTTP request same as AJAX, except the answer on request will happen now or some time in the future defined by application logic. Supported in all major browsers.
WebSockets - client ↔ server. Create TCP connection to server, and keep it as long as needed. Server or client can easily close it. Client goes through HTTP compatible handshake process, if it succeeds, then server and client can exchange data both directions at any time. It is very efficient if application requires frequent data exchange in both ways. WebSockets do have data framing that includes masking for each message sent from client to server so data is simply encrypted. support chart (very good)
Overall, sockets have much better performance than long polling and you should use them instead of long polling.
This question concerns socket.io versions < 0.9.x.
Newer versions have different transports and methods of setting transports.
I test node js and socket.io in two week. when I began I get the problem from socket.send(message) function in client. I can't send any message to the server. But I still can receive messages from the server. I solved this problem when I found the configure transport of server side:
socket.set('transports',[
'xhr-polling'
, 'jsonp-polling'
]);
Everything good. Now I can send messages to the server as well. But I still have a question why I have to configure transport. Default socket.io use websocket transport setting like this:
socket.set('transports', [
'websocket'
, 'flashsocket'
, 'htmlfile'
, 'xhr-polling'
, 'jsonp-polling'
]);
so it uses websocket at first, not xhr-polling. But the server cannot receive any messages sent from the client when using socket.send(msg) even socket.emit(...).
So the problem is: what is not supporting websocket here? browser or node.js ... I'm sorry but I searched so many pages from google and I haven't found an answer for this.
I use node.js version 0.8.16, socket.io version 0.9.13 and newest browsers: chrome, firefox, opera
I want to use websocket not xhr-polling.
That's odd because even if websockets are not supported by your server configuration, socket.io will select the next best available method (in your case xhr-polling). Actually, you shouldn't even need to set those transports as socket.io will try to use 'websocket' as a primary method by default. This may indicate some other problem, possibly with your code?
What is not supporting websockets is definitely not the browsers you're using nor node.js of course. This will depend on your server setup.
First check:
The port you're listening to is open in your firewall
Your webserver supports websockets. If you're using Apache and proxing your request to an internal IP:PORT, websocket will not work unless you install something like apache-websocket or pywebsocket
What finally solved my issue was to disable Apache listening on port 80 and having node.js listening on that port. Here's the answer on SO that helped me: https://stackoverflow.com/a/7640966/2347777
I have a problem with websockets and socket.io. When I try to connect to my node server with socket.io it initially connects using websockets but when reverts to jsonp-polling shortly after.
This is the output from the node sever when I connect:
8 Jun 07:01:15 - Initializing client with transport "websocket"
8 Jun 07:01:19 - Initializing client with transport "jsonp-polling"
8 Jun 07:01:19 - Client 16630339180119336 connected
This happens in Chrome & Safari.
I have updated to the latest socket.io version 0.6.17 and am running node 0.4.7.
I have tried deleteing my cookies and cache as suggested on github and SO, however the problem remains. Also, when I try to force websockets it never fully connects with a session ID.
Does anyone have any ideas?
Websocket API is not supported by default in all the browsers at the moment (as per my knowledge) it should work on chromium though try testing it on chromium or firefox(after editing the default settings)and see if that still reverts to XHRPolling.
I am running it on a different IP as I need to run node on port 80 which causes conflict on my web server with Apache. Can websockets/flashsockets not be use cross-domain?
Now there might be 2 different reasons for the bug from here
Web/Flash Sockets will not let u connect to the node.js client unless either u specify a differnt port like 81 or u specially specify apache to proxy the incoming request to Node.
an easy solution could be writing the Node.js based HTTP server to just relay data from Apache (and setting Apache to run on a differnt port then 80)
This link tells how to do that... in this process you can make Node.js do something like check if the request is from a websocket/httpbrowser if thats an http browser forward the request to Apache if not ie if thats from web/flash sockets then handle the socket accordingly. or as commented on the question. Specify APACHE to proxy to Node.js.
Flashsockets require you to serve a crossdomain policy file on port 843 are you sure you are providing a cross domain file? (I think socket.io has inbuilt functionality to do that but still its always good to check.)
As told on the socket.io main website
In order to provide realtime connectivity on every browser, Socket.IO selects the most capable transport at runtime, without it affecting the API.
WebSocket
Adobe® Flash® Socket
AJAX long polling
AJAX multipart streaming
Forever Iframe
JSONP Polling
It's pretty clear that it will revert to AJAX Long Polling if websockets are disabled and Adobe Flash Socket fails to connect (this might be due to the unavailability of the policy file).
Here's a sample code for the cross domain file which you can include in your code and see if that makes your server run with websockets.
var net = require("net");
// Node.js
var Policy = net.createServer(function(socket)
{
socket.setEncoding('utf8');
socket.on('connect',function(){
console.log("Policy Request");
socket.end("<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" secure=\"false\"/></cross-domain-policy>");
});
});
Policy.listen(843);