The problem:
I just successfully got my Node.js server all working properly and setup with SSL, great. But then I see this:
[blocked] The page at 'https://www.mywebsite.com/' was loaded over
HTTPS, but ran insecure content from
'http://54.xxx.xxx.77:4546/socket.io/socket.io.js': this content
should also be loaded over HTTPS.
I tried just changing the socket.io URL to https, but of course that doesn't work because socket.io insists on serving it's own generated file, a process that I don't think I control. I can change the port that socket.io listens on, that's it.
The question:
So how do I securely serve socket.io (1.0)?
The codez:
var port = 4546;
var io = require('/node_modules/socket.io')(port);
As a side note, I think socket.io (its back and forth communication) should run over HTTPS properly without any extra work; I read that somewhere. Perhaps someone can confirm. It is important that the web socket's communications be securely transferred.
This is one of those occasions where the questions aren't quite duplicates, but the selected answer to a question answers this one as well.
It's simple: Just pass your https server object as socket.io's port parameter.
// ... require stuff
var app = express();
// ... set up your express middleware, etc
var server = https.createServer(sslOptions, app);
// attach your socket.io server to the express server
var io = require("socket.io").listen(server);
server.listen(port);
code by aembke
Related
I have a Nodejs based server, which uses a middleware which basically redirects the user to a CAS to manage authentication. The CAS server responds with a ticket, and finally my Nodejs server trades the ticket for an user object with the CAS and stores it in a session.
This process works perfectly fine without cluster.
Today i wanted to clusterize my Nodejs server, using https://nodejs.org/api/cluster.html (thing that i've already done without any problem).
So instead of having:
let server = http.createServer(app);
server.listen(PORT, HOST, () => {
// Keep a track that the server has been launched
logger.log(`Server running at http://${HOST}:${PORT}/`);
});
Where everything was working fine, I now have:
if(cluster.isMaster) {
// This is the mother process
// Let's create the threads
for(let i=0; i < NB_WORKERS; i++) {
cluster.fork();
}
// When a child crash... Restart it
cluster.on('exit', (worker, code, signal) => {
logger.log("info", "A child died (PID: %s). It was killed by code or signal %s. Restarting...", worker.process.pid, dode || signal);
cluster.fork();
});
} else {
// This is a child
// Create the server, based on http
let server = http.createServer(app);
server.listen(PORT, HOST, () => {
// Keep a track that the server has been launched
logger.log(`Server running at http://${HOST}:${PORT}/`);
});
}
When i launch the server, it actually starts the server on NB_WORKERS threads, as expected. But when i want to access the app delivered by my Node server with my browser, i have the following error:
which says if you can't see:
XMLHttpRequest cannot load https://localhost:8443/cas/login?
service=http://localhost:3000. No 'Access-Control-Allow-Origin' header is
present on the requested resource. Origin 'http://localhost:3000' is
therefore not allowed access
https://localhost:8443 is were my CAS server is running, and http://localhost:3000 is where my Node server is running.
Note that if i set NB_WORKERS to 1, everything works fine again.
I understand that setting 'Access-Control-Allow-Origin' header in my CAS server config would probably make everything works fine, but i don't understand why it's working with one thread and not with two or more.
What am i missing ?
I finally managed to make it work, so i post here in case someone come across a similar issue.
About Node session
As I said, my Nodejs server stores datas into a session. In this case, it was a simple express-session with the default MemoryStore since i'm still in development.
When clustering, express-session default store is NOT shared between threads. This means that requests supposed identified by the session were sometimes not, depending of which thread handled the request. This caused the authentication middleware to ask the CAS server again.
To make it work, i had to use a persistant store for my session, such as Redis.
About the CORS issue
I'm not really sure about what caused this strange issue, but here what i thought about:
Since my CAS server uses HTTPS protocol, some handshakes were made between both servers due to TSL (Hello, Certificate exchange, Key exchange). Maybe these were broken if not only one thread tried to do it (One makes the Hello, then the response is sent to another thread => this can't work).
Still it's very strange, because it's very low-level, and almost none cluster Node app would work if this was not managed internally.
In order to make the previous handshakes work, i guess that each thread must be identified somehow. A likely way to do it is to assign a custom port to each thread: the process still run on port 3000, but each thread use a custom one to communicate.
Maybe this was causing my issue, but I can't really tell precisely.
So, i just had to manage correctly my session store to make it work. But please let me know if i was wrong somewhere about the CORS issue.
I have two subdomains:
socket.mydomain.com - The Socket.IO server
app.mydomain.com - A web app that I'd like to connect to my web socket.
In the landing page for app.mydomain.com, I've linked in the Socket.IO client script, and successfully created an IO object, like so:
<script src=https://socket.mydomain.com/socket.io/socket.io.js></script>
<script type=text/javascript>
const socket = io();
socket.on('message', data => console.log(data));
</script>
However, rather than trying to connect to socket.mydomain.com, the client is trying to connect to app.mydomain.com. Because there's no socket at app.mydomain.com, it fails and keeps retrying.
Is there a way to connect my app at app.mydomain.com to my socket at socket.mydomain.com? Or is this impossible?
Update
Most all of the existing answers relating to this question now use outdated code, because Socket.IO has recently upgraded to 1.0 (1.4 in fact). However, even taking these code changes into account, it seems like Socket.IO doesn't allow what I'm trying to do.
When Socket.IO makes the initial connection to the server, it sends an XHR with the withCredentials settings set to true. But you can't have withCredentials set to true and allow CORS on the server. So it seems my question is actually, 'Is there a way around this problem?'
To accomplish what I wanted, several things needed to be configured correctly, and I needed to use the latest Socket.IO syntax:
CORS must be enabled on the socket.mydomain.com server (Access-Control-Allow-Origin header set to *)
Acceptable transports had to be specified on the server (socket.mydomain.com):
const app = express();
const server = http.createServer(app);
const io = require('socket.io')(server, {
transports: ['websocket', 'xhr-polling']
});
The remote server and acceptable transports had to be specified on the client (app.mydomain.com):
const socket = io('socket.mydomain.com', {
transports: ['websocket', 'xhr-polling']
});
Edit: I'm sending data from app A to app B over web socket, where A is a router app for something else, and B is my web app. When B receives the data, it should send it to any client viewing its home page, also over web socket. But, since the roter app and the home page clients are connected to the same web socket server, I don't know which connections are to the clients viewing the home page, and which connections are to other stuff, like my router. Only the home page clients should receive the data.
I basically want to pass the logging data recieved from my router to my home page in real time so I can view it.
========
I have an express app that server a simple html page. It runs this script:
var host = window.document.location.host.replace(/:.*/, '');
var ws = new WebSocket('ws://' + host + ':5000');
ws.onmessage = function (event) {
console.log(JSON.parse(event.data));
};
In the nodejs backend, I have a simple ws server running, listening for connections:
var WebSocketServer = require("ws").Server;
module.exports.init = function(server) {
var wss = new WebSocketServer({ server: server });
wss.on('connection', function (ws) {
...
});
};
I get connections from, at the moment, 2 different locations.
A router app I have running that is sending this web app logging messages.
The html-page that this web app is serving.
I want to pipe the data from the router app to my html page, but to do that I need to know which of my connections I need to pipe the data to. I can in theory have many connection, but only one of them, at least for now, should be passed the data after it is recieved.
I thought I could compare the origin of the web socket connection to the domain of the web server the web socket server ran on.
I can get the origin of the connection like this: ws.upgradeReq.headers.origin. That will return e.g: localhost:5000. But I don't know the domain name where my web socket server is running. I've tried to google, and it seems like to get the domain name, I need to get it from an http request. What I am looking for is something that just gives me the name, without having to wait for an http request.
I've tried os.hostname(), but it doesn't give me the results I need.
I've also tried server.address(), where server is var server = require("http").createServer(app);, but that gives me this: { address: '::', family: 'IPv6', port: 5000 }.
Isn't there just a way to get the host and port? Can I somehow use the address part above to get the host name?
The web app will probably run on Heroku.
Based on your recent comments, it sounds like each browser client that connects a webSocket should just tell your server what web page it is looking at with an initial message and the server should keep track of that for each active connection.
In socket.io (built on top of webSockets), you could just connect to the /homepage namespace and then that server could broadcast to all sockets connected to that namespace. You could, of course, implement that type of functionality yourself with a plain webSocket.
Then, your server would not only have a list of connected sockets, but could also know what page they were all from. That would allow you to broadcast based on current page. Your server-to-server webSocket would not have sent a message that it's from the home page, so it would not be tagged as such and you could avoid sending to it.
You might find socket.io easier to use for all of this. In additon to namespaces on both client and server, it also gives you automatic reconnection from browsers, a simpler message passing system, server-side broadcast to namsepaces and so on.
everyone!
I'm in an issue.
I'm trying to connect in a server with my client application.
My server is hosting by Modulus that have no port to access URL.
When I try to call io('my-application.onmodulus.net'), the browser (chrome) returns me a failed GET net::ERR_CONNECTION_REFUSED with a PORT setted in GET URL.
I tested the URL without the PORT in the browse and works.
There's a way to GET socket.io connect from then client without socket.io set a PORT in URL?
Here's my code:
I try of different ways to call this method, and I don't find OPTIONS parameter documentation.
var socket = io('my-application.onmodulus.net', {multiplex: false, path: '/socket.io'});
Thank you all so much!
In socket.io, if you pass anything as the first argument to io() to specify the hostname, then it should be in URL form, not just a hostname.
If you don't pass anything (e.g. you just call io()), then socket.io will connect to the same host and port as the current web page.
So, you could use:
var socket = io('http://my-application.onmodulus.net/socket.io', {multiplex: false});
or, if you're connecting to the same host and port as your web page, you could just leave out the URL and use:
var socket = io({multiplex: false});
I need to share same http server between socket.io and websocket (from 'ws' package) handlers.
Unfortunatelly, despite that they are listening to diffrent prefixes, the first is listening to /socket.io and the second to /websocket urls, for some reasons if they are running on the same server the websocket is not working properly.
I did some debugging, but it seems that the requests are properly handled by both libraries but in the end only socket.io works properly.
Any idea how to solve that?
The way sockets work in node.js is quite a bit different from the way normal requests work. There is no routing, so rather than listening to a url, you have to listen to all sockets. The default behavior of socket.io is to close any socket connections that it doesn't recognize. To fix this, you'll need to add the flag 'destroy upgrade': false to the options (server is an express server):
require('socket.io').listen(server, {'destroy upgrade': false, ...})
You'll also need to check the url when a client connects (in the code handling /websocket) and ignore it if it looks like it belongs to socket.io. You can find the url from the client object (passed in to the on connection handler) as client.upgradeReq.url.
Ok solution is simple (unfortunately half day of debugging and now it's simple :)).
There is an option 'destroy upgrade' for upgrade requests coming from non-socketio clients. Since Websocket (module 'ws') is using the same requests some of them might be for 'ws' not for 'socket.io'. So this option should be disabled.
io = require('socket.io').listen(server, { log: false, 'resource':'/socket.io' });
io.disable('destroy upgrade')
Update for 2016:
io.disable('destroy upgrade');
seems not to be available anymore.
But I succeeded by assigning the websocket module ws a path (using Express):
var wss = new WebSocketServer({ server: server, path: '/ws' }); //do not interfere with socket.io
Of course the client has the same path ws://theserver.com/ws
I did not have to alter the socket.io side at all.