How to use existing socket for a NodeJS HTTP server? - node.js

I made a proof of concept Reverse HTTP implementation using NodeJS.
However reusing client's socket required me to set a private member property; is there a correct way to do it?
server._socket = socket;
server.emit('connection', server._socket);
NodeJS HTTP docs tell about server.listen(socket), but it didn't work; it threw an exception about illegal parameter.
Full sample code for both client & server available at: https://github.com/norjs/portal-service/blob/12-reverse-http-support/samples/reverse-http/client.js#L57

Related

socket.io client doesn't set sid in requests

I'm trying to build a nodejs script to communicate with a socket.io server.
const io = require('socket.io-client');
const socket = io('http://192.168.144.249');
socket.on('connect', () => {
console.log('connected');
})
Using wireshark to follow traffic I can see the following:
So the browser sends me a sid, both in the response body and in the cookie.
Unfortunately, my following requests do not include this sid and I received a 400 Bad Request error:
When I try to build the same client from a browser windows I can see that this cookie is indeed set, both as cookie and as a query parameter:
I don't want to use a browser, I want to use a standalone node script. As far as I understood the parsing of the response and the inclusion of the session id should be done automatically by the socket.io-client. Am I wrong? If so, how can I intercept this event so that I can send the sid with following requests?
Am I supposed to first do a simple http request to the server, get the sid from there and then add it to the socket.io client when creating it using custom cookies or custom query parameters?
I can also see that the node standalone script is using engine.io version 4 (EIO=4 in the GET requests), while the browser is doing it with engine.io version 3, but the respose received seems to be exactly the same so I don't really think this is what is preventing my script from automatically completing the handshake with the server.
Well, I was wrong. It was indeed a protocol mismatch problem.
I was able to make my script working using socket.io-client#1.0.2. Install it with:
npm install socket.io-client#1.0.2

Connect to a socket.io server from a Node.js server using the 'ws' package

I have a Node.js server which utilizes the popular ws package for using web sockets. I'd like to use this library to connect to an third party server which is running socket.io.
If I were to use socket.io on my server, the connection code would be something like this:
const socket = socketIo('https://api.example.com/1.0/scores')
I've attempted to connect to the same service using the ws package, and modifying the url:
const wsClient = new WebSocket('wss://api.example.com/1.0/scores');
but this results in the following:
Error: Unexpected server response: 200
Question:
What needs to be done to connect to a third party server running socket.io from a server running the ws package?
Additional Info:
I've noticed in my searches that some people have suggested appending
/socket.io/?EIO=3&transport=websocket to the end of the url. This
does not throw the same error as above (> Error: Unexpected server
response: 200) nor throw any visible error, but does not appear to
work (no data is received from the remote server).
Using new WebSocket('ws://api.example.com/1.0/scores?EIO=3&transport=websocket'); to open the connection (via ws) results in the following stack trace:
{ Error: Parse Error
at Socket.socketOnData
at emitOne
at Socket.emit
// ...
}
The socket.io api utilizes websockets but it also has a lot of other functions built on top of it in order to do things such as HTTP handshakes, session ids, and it can even handle fail overs to other protocols when needed.
You got half of the issue so far. Adding the line socket.io/?EIO=3&transport=websocket you're specifying parameters for the socket.io server to take.
EIO=3 specifies the version number for engine.io in which socket.io is using. In this case you are saying engine.io version = 3
transport=websocket specifies which transport protocol to use. As i said earlier, socket.io uses other protocols in cases such as fail overs. This portion forces socket.io to use websocket as the preferred protocol.
Now the next half is the WebSocket. WebSocket allows for Extensions which includes different kinds of compression that are commonly used when sending data. Which I believe is what is causing your Parse Error
Try this (found here):
const WebSocket = require('ws');
const ws = new WebSocket('ws://server/socket.io/?EIO=3&transport=websocket', {
perMessageDeflate: false
});
By setting perMessageDeflate: false you are specifying "Do not compress data". Since as i said this is a WebSocket Extension there are different variations as well. Try these instead if it doesn't work
x-webkit-deflate-frame
perframe-deflate
As a disclaimer this information is from the research that I have done. Im not a "socket.io specialist" so if there's anything incorrect please comment and i'll edit the post.
Because Socket.IO doesn't guarantee that there will be a WebSockets server hosted like you're seeming to expect, you should instead use their standard client package.
npm i socket.io-client
Then use the package in your code:
const ioClient = require('socket.io-client')('https://example.com/1.0/scores')
The full docs for socket.io-client are available on their GitHub repo.
Note: Honestly, though, it's just better at this point to use WebSockets instead if possible. WebSockets has become well-supported in browsers and is quite standard. Socket.IO is rarely necessary and could add some overhead.

socket.io: Is is possible to intercept a join namespace request?

My Node.js knowledge is fairly basic. I'm using socket.io to create a server and socket.io-client-swift in an iOS app.
I would like the server to create a dynamic namespace depending on the users selection on the client app. The idea being that all users with a specific selection join the same namespace on the server. This allows messages to be broadcast to all of those users within the namespace.
The problem is that the socket.io documentation and code appears to assume that you will be hardcoding the namespace name in the node.js code and that the namespace will be defined before the client iOS code attempts to join it.
Here's what i would like to occur:
Client requests to join a namespace called 'abc'.
Server receives request.
Something on the server checks the request, figures out there is no current namespace for 'abc' and dynamically allocates one.
Server finishes processing the join request, connecting the socket to the newly created namespace.
It's step #3 that I'm having problems with. I've tried using io.use(function(socket, next){...} to intercept the incoming request, but it appears that occurs after the server has determined whether there is a matching namespace for the request.
Does anyone have any idea how to intercept an incoming namespace request?
I think I figured it out. I added some code to my server.js like this:
// Lets Swizzle in some wrapper code.
//var originalConnection = Client.prototype.connect;
Client.prototype.originalConnect = Client.prototype.connect;
Client.prototype.connect = function(name){
var nsp = io.nsps[name];
if (!nsp) {
io.of(name);
}
this.originalConnect(name);
};
Effectively I swizzled in a wrapper function that does the dynamic namespace creation.

Websocket server running fine but cannot connect from client (what url should I use?)

OK this is very simple to anyone who's used websocket and nodejs.
I have created a websocket server named ws_server.js and put it in C:\Program Files (x86)\nodejs where I have installed the nodejs framework. I started the server and it is running and it says it's listening on port 8080. So far so good, I have the server running.
Now I simply want to connect to it from client code so that I can do all that lovely stuff about capturing events using event listeners etc. The problem is, embarassingly, I cannot figure out what URL to use to connect to my websocket server.
function init() {
testWebSocket();
}
function testWebSocket() {
websocket = new WebSocket("ws://localhost:8080/"); // WHAT URL SHOULD BE USED HERE?
websocket.onopen = function(evt) { alert("OPEN") };
websocket.onclose = function(evt) { alert("CLOSE") };
websocket.onmessage = function(evt) { alert("MESSAGE") };
websocket.onerror = function(evt) { alert("ERROR") };
}
function doSend(message) {
// this would be called by user pressing a button somewhere
websocket.send(message);
alert("SENT");
}
window.addEventListener("load", init, false);
When I use ws://localhost:8080 the only events that trigger are CLOSE and ERROR. I cannot get the client to connect. I must be missing something very simple. Do I need to set up my nodejs folder in IIS for example and then use that as the URL?
Just to reiterate, the websocket server is running fine, I just don't know what URL to use to connect to it from the client.
EDIT: The websocket server reports the following error.
Specified protocol was not requested by the client.
I think I have got it working by doing the following.
var websocket = new WebSocket("ws://localhost:8080/","echo-protocol");
The problem being that I needed to specify a protocol. At least now I get the onopen event. ...if nothing much else
I was seeing the same error, the entire web server goes down. Adding the protocol fixes it but leaves me wondering why it was implemented this way. I mean, one bad request should not bring down your server.
You definitely have to encase it a try/catch, but the example code provided here https://www.npmjs.com/package/websocket (2019-08-07) does not. This issue can be easily avoided.
I just wanted to share a crazy issue that I had. I was able to connect to a websocket of an old version of a 3rd party app in one computer, but not to a newer version of the app in another.
Moreever, even in new computer with the new version of the app, The app was able to connect to the websocket, but no matter what I did, when I tried to connect with my own code, I kept getting the error message that the websocket connection failed
Long story short, They changed an apache configuration that allowed connecting to the websocket via a proxy.
In the old version, apache config was:
ProxyPass /socket/ ws://localhost:33015/ retry=10
ProxyPass /socket ws://localhost:33015/ retry=10
In the new version, apache config was changed to:
ProxyPass /socket/ ws://localhost:33015/ retry=10
By bad luck, I was trying to connect to ws://localhost/socket and not to ws://localhost/socket/. As a result, proxy was not found, and connection returned an error.
Moral of the story: Make sure that you are trying to connect to a websocket url that exists.
For me, the solution was to change the URL from ws:// to wss://. This is because the server I was connecting to had updated its security, and now only accepted wss.

nodejs tls session id

I am using TLS to create session using node.js library. Node.js does it provide a way to retrieve session id of TLS connection established. It is part of SSL ctx in openssl.
Can it be done without using connect, express or geddy?
Unfortunately I don't think that information is exposed from the SSL context for node connections.
You can access the node object representing the context as follows:
var con = tls.connect(..., ...);
con.pair.credentials.context
Unfortunately, the only methods available on that object are setKey, setCert, addCACert, addCRL, addRootCerts, setCiphers and setOptions.
That said, with a little bit of C++ and SSL know-how and come copy/pasting, you could probably patch node's node_crypto.cc and node_crypto.h files to add that lookup without TOO much work.
You can't get the session_id but you can get the session itself for caching / resume purposes by calling conn.getSession() once the connection is established.

Resources