Node.js HTTP agent pre establish tcp connection - node.js

So I'm working on an application where there are a lot of network requests to be sent and so I have set up an http agent to reuse tcp connections like so, which would be used all over my application :
const https = require("https");
global.httpsAgent = new https.Agent({
keepAlive: true,
})
This will allow me to reuse tcp sockets that have already been created so I don't have to create them again and save time when dealing with lots of http requests. But before I send any http requests there will not be any tcp connections. So the first time I send an http request I have to wait till the tcp connection is established. Is there a way to tell the https agent to maintain a minimum number of free tcp sockets to a domain at all times ? Right now when my applications starts up I'm just sending meaningless http requests so that the tcp connections are established and kept-alive like so :
function establishTcpConns(numConns) {
let arr = [];
for(let i=0; i<numConns; i++) {
arr.push(
// fetch is imported like so : const fetch = require("node-fetch");
fetch("https://example.com", {
agent: url => global.httpsAgent,
headers: {
"connection": "keep-alive"
}
})
);
}
return Promise.allSettled(arr);
}
I see that there a function called agent.createConnection(options[, callback]) defined in the nodejs docs https://nodejs.org/api/http.html#agentcreateconnectionoptions-callback
Is this what I'm looking for? If so how would I use it?

Related

why does Chrome connect to my nodejs server from 2 ports?

my nodejs script is:
const http = require("http");
const httpserver = http.createServer();
httpserver.on('connection', socket=>{
console.log(socket.remotePort, socket.address());
})
httpserver.listen(8080)
when navigating to the url: http://localhost:8080/ in Chrome I see that 2 connections are established on consecutive remote ports ex:
55413 {address: '::1', family: 'IPv6', port: 8080}
55414 {address: '::1', family: 'IPv6', port: 8080}
I'm confused as to why it is establishing 2 connections. Other browsers don't do this. Any thoughts are greatly appreciated. Thanks
I updated the node code to the following:
const http = require("http");
const httpserver = http.createServer();
httpserver.on('connection', socket=>{
console.log(socket.remotePort, socket.address());
socket.on('data',(data)=>{
console.log(socket.remotePort, socket.bytesRead);
console.log(data.toString())
})
socket.on('end',(data)=>{
console.log(socket.remotePort, 'ended');
})
})
httpserver.on('request', (req,res)=>console.log('request is made made'));
httpserver.listen(8080);
after refreshing the page a few times it seems that Chrome establishes 2 socket connections and uses the first to receive the GET request. The second receives no data. Upon refresh of the page it ends the first socket connection ( that was used for the GET request); opens a new socket connection (3rd) then uses the 2nd created for the new GET request. On each consecutive refresh it creates a new socket connection and uses the previous opened one, while ending the socket used for the previous GET request.
So it seems Chrome is creating a second socket in anticipation of a page refresh?

Websocket closing after 60 seconds of being idle while connection node server using AWS ELB

I have one node server running on EC2 instance and client is also running on same EC2 instance, Client open websocket connection to communicate node server, it is working in QA and Dev AWS environment but same web connection is getting close after 60 seconds of being idle in prod environment ,I am running client and node server behind ELB in aws environment.
Client Code:
ws = new WebSocket('ws://localhost:8443');
ws.onclose = function () {
console.log("Websocket connection has been closed.");
clientObj.emit('LogoffSuccess', 'LogoffSuccessfully');
};
ws.onerror=function(event)
{
console.log(event.data);
};
ws.addEventListener('open', function (event) {
console.log('Websocket connection has been opened');
ws.send(JSON.stringify(loginCreds));
});
Node server Code below:
const wss = new WebSocket.Server({ server: app });
const clients = {};
const idMap = {};
wss.on(`connection`, ws => {
const headers = ws.upgradeReq.headers;
const host = headers.host;
const key = ws.upgradeReq.headers[`sec-websocket-key`];
ctiServer.on(`responseMessage`, message => {
clients[message.AgentId].send(JSON.stringify(message));
});
ws.on(`message`, message => {
log.info(`Message received. Host: ${host}, Msg: ${message}`);
if (JSON.parse(message).EventName === `Login`) {
clients[JSON.parse(message).AgentId] = ws;
idMap[key] = JSON.parse(message).AgentId;
}
ctiServer.processIncomingRequest(message);
});
ws.on(`close`, () => {
log.info(`Connection closed. Host: ${host}`);
const message = {
EventName: `Logoff`,
AgentId: idMap[key],
EventData: {}
};
});
});
By default, Elastic Load Balancing sets the idle timeout value to 60 seconds. Therefore, if the target doesn't send some data at least every 60 seconds while the request is in flight, the load balancer can close the front-end connection. To ensure that lengthy operations such as file uploads have time to complete, send at least 1 byte of data before each idle timeout period elapses, and increase the length of the idle timeout period as needed.
https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#connection-idle-timeout
Note that your interests are best served by periodically sending traffic to keep the connection alive. You can set the idle timeout to up to 4000 seconds in an Application Load Balancer, but you will find that stateful intermediate network infrastructure (firewalls, NAT devices) tends to reset connections before they are actually idle for so long.
PING!
Write a ping implementation (or a nil message implementation)...
...otherwise the AWS proxy (probably nginx) will shut down the connection after a period of inactivity (60 seconds in your case, but it's a bit different on different systems).
Do you use NGINX? Their requests timeout after 60 seconds.
You can extended the timeout in the NGINX configuration file for your websockets specific location.
In your case it could look something like this when extending the timeout to an hour:
...
location / {
...
proxy_pass http://127.0.0.1:8443;
...
proxy_read_timeout 3600;
proxy_send_timeout 3600;
...
}
Also see this website for more information:
https://ubiq.co/tech-blog/increase-request-timeout-nginx/
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_send_timeout

Node JS TCP Proxy: Reuse socket in callback function

I'm trying to implement a TCP proxy in Node JS. I only have some experience with Javascript so I met a lot of problems along the way. I've done a lot of searching for this one but had no luck.
The problem occurs when browser sends a CONNECT request for HTTPS. My proxy will parse the host name and port, and then create a new socket that connects to the server. If all these steps went well, I will start forwarding message.
Part of my code looks like this:
var net = require('net');
var server = net.createServer(function(clientSock) {
clientSock.on('data', function(clientData) {
var host = // get from data
var port = // get from data
if (data is a CONNECT request) {
// Create a new socket to server
var serverSock = new net.Socket();
serverSock.connect(port, host, function() {
serverSock.write(clientData);
clientSock.write('HTTP/1.1 200 OK\r\n');
}
serverSock.on('data', function(serverData) {
clientSock.write(serverData);
}
}
}
}
Since the CONNECT request needs both client socket and server socket open until one side closes the connection, the code above doesn't have this behavior. Every time I receive some data from client, I will create a new socket to server and the old one is closed.
Is there a way to store the server socket as a global variable so that the data event handler can reuse it? Or is there any other way to solve this?
Thanks a lot!!!!
You can just move the variable up to a higher scope so it survives across multiple events and then you can test to see if its value is already there:
var net = require('net');
var server = net.createServer(function(clientSock) {
var serverSock;
clientSock.on('data', function(clientData) {
var host = // get from data
var port = // get from data
if (data is a CONNECT request) {
// Create a new socket to server
if (!serverSock) {
serverSock = new net.Socket();
serverSock.connect(port, host, function() {
serverSock.write(clientData);
clientSock.write('HTTP/1.1 200 OK\r\n');
}
serverSock.on('data', function(serverData) {
clientSock.write(serverData);
}
} else {
serverSock.write(clientData);
}
}
}
}

TCP and WEB sockets

I have http server and socket.io (listening this http server). Clients connect(via socket io) and get some information. Now I want to have clients connecting via tcp socket that will receive the same information as the clients on web socket. How to do it? Is it required to create a net server? And if so, then how information which come to http server send to tcp clients?
You need to create the TCP server so clients will be able to connect to it.
One solution can be using a messaging system (such as pub/sub with Redis, or a library like https://github.com/learnboost/kue) to notify the other server to send the data.
For example:
1) user connects to socket.io
2) user connects to TCP server
3) TCP server subscribes to listening to signals
4) socket.io emits data to the user and signals the TCP server to send the data as well
5) TCP server sends the data
in nodejs to start a tcp server:
var fs = require('fs');
var net = require('net');
var server = net.createServer(function(socket){ // create a tcp server
socket.on('data',function(data){ // on data event when data is set to the socket
var strRequestInfo = data.toString(); // get the string sent by the client
/*
here you could analyse the request data
and think what to do with it like return a certain file
*/
fs.readFile('/path/to/some/file.html', function (err, fileData) { // read a file
if (err) throw err;
socket.write(fileData); // write file content to tcp socket
});
/* -or- just write some text */
socket.write(new Buffer('some text'));
});
});
server.listen(8080, function() { // bind the server
console.log('TCP server bound');
});
you have to take in to consideration that socket.on('data') will not trigger when all the data is sent, it can trigger many time depending on the size of the data being sent.
Therefore the request data should be concatenated until the logic of your request decides to send a response back to the client.
You can add the sockets to an array if you would like to send data to all sockets:
var socketArray =[];
var server = net.createServer(function(socket){
socketArray.push(socket);
});
then you could iterate and send responses to all client:
for(var i=0;i<socketArray.length;i++)
socketArray[i].write(new Buffer('some data'));

Is it possible to enable tcp, http and websocket all using the same port?

I am trying to enable tcp, http and websocket.io communication on the same port. I started out with the tcp server (part above //// line), it worked. Then I ran the echo server example found on websocket.io (part below //// line), it also worked. But when I try to merge them together, tcp doesn't work anymore.
SO, is it possible to enable tcp, http and websockets all using the same port? Or do I have to listen on another port for tcp connections?
var net = require('net');
var http = require('http');
var wsio = require('websocket.io');
var conn = [];
var server = net.createServer(function(client) {//'connection' listener
var info = {
remote : client.remoteAddress + ':' + client.remotePort
};
var i = conn.push(info) - 1;
console.log('[conn] ' + conn[i].remote);
client.on('end', function() {
console.log('[disc] ' + conn[i].remote);
});
client.on('data', function(msg) {
console.log('[data] ' + conn[i].remote + ' ' + msg.toString());
});
client.write('hello\r\n');
});
server.listen(8080);
///////////////////////////////////////////////////////////
var hs = http.createServer(function(req, res) {
res.writeHead(200, {
'Content-Type' : 'text/html'
});
res.end(['<script>', "var ws = new WebSocket('ws://127.0.0.1:8080');", 'ws.onmessage = function (data) { ws.send(data); };', '</script>'].join(''));
});
hs.listen(server);
var ws = wsio.attach(hs);
var i = 0, last;
ws.on('connection', function(client) {
var id = ++i, last
console.log('Client %d connected', id);
function ping() {
client.send('ping!');
if (last)
console.log('Latency for client %d: %d ', id, Date.now() - last);
last = Date.now();
};
ping();
client.on('message', ping);
});
You can have multiple different protocols handled by the same port but there are some caveats:
There must be some way for the server to detect (or negotiate) the protocol that the client wishes to speak. You can think of separate ports as the normal way of detecting the protocol the client wishes to speak.
Only one server process can be actually listening on the port. This server might only serve the purpose of detecting the type of protocol and then forwarding to multiple other servers, but each port is owned by a single server process.
You can't support multiple protocols where the server speaks first (because there is no way to detect the protocol of the client). You can support a single server-first protocol with multiple client-first protocols (by adding a short delay after accept to see if the client will send data), but that's a bit wonky.
An explicit design goal of the WebSocket protocol was to allow WebSocket and HTTP protocols to share the same server port. The initial WebSocket handshake is an HTTP compatible upgrade request.
The websockify server/bridge is an example of a server that can speak 5 different protocols on the same port: HTTP, HTTPS (encrypted HTTP), WS (WebSockets), WSS (encrypted WebSockets), and Flash policy response. The server peeks at the first character of the incoming request to determine if it is TLS encrypted (HTTPS, or WSS) or whether it begins with "<" (Flash policy request). If it is a Flash policy request, then it reads the request, responds and closes the connection. Otherwise, it reads the HTTP handshake (either encrypted or not) and the Connection and Upgrade headers determine whether it is a WebSocket request or a plain HTTP request.
Disclaimer: I made websockify
Short answer - NO, you can't have different TCP/HTTP/Websocket servers running on the same port.
Longish answer -
Both websockets and HTTP work on top of TCP. So you can think of a http server or websocket server as a custom TCP server (with some state mgmt and protocol specific encoding/decoding). It is not possible to have multiple sockets bind to the same port/protocol pair on a machine and so the first one will win and the following ones will get socket bind exceptions.
nginx allows you to run http and websocket on the same port, and it forwards to the correct appliaction:
https://medium.com/localhost-run/using-nginx-to-host-websockets-and-http-on-the-same-domain-port-d9beefbfa95d

Resources