multiple tcp connections despite of http-alive - javascript

I'm not sure whether I understand http-keep-alive correctly, In my opinion, it should reuse the tcp connection, not building a new one. However, I found something really strange, it seems like it is hard to anticipate the behavior of http keep-alive.
Server: NodeJS & Express ^4.16.3
and I have used Wireshark to analyze the results
Situation 1:
Server-side
for(let i =1; i<11; i++){
app.use('/' + i, (req, res) => {
res.header('cache-control', 'no-store');
res.send('i');
});
}
server.keepAliveTimeout = 50000;
Client side
setTimeout(() => {
for (let i = 1; i < 11; i++) {
fetch('' + i).then(data => console.log(data));
}
}, 10000);
result: tcp connection is reused(only one tcp connection), all fetch requests reuse the tcp connection established by index.html
Situation 2:
Client side codes are the same, only server side codes change here
for(let i =1; i<11; i++){
app.use('/' + i, (req, res) => {
res.header('cache-control', 'no-store');
// here I have added timeout!
setTimeout(() => {
res.send('i');
}, 2000);
});
}
result: 5 more tcp connection are established(in the picture only 4, because the screenshot is not complete), despite that I have set server.keepAliveTimeout = 50000;
So my question is, what does http keep alive really mean? why it behaves like this?
If it will not use the same tcp connection in situation 2, what is the meaning of keep alive??
appreciate for any thoughts!

Yes, HTTP Keep Alive should reuse your TCP connection with the server. The server append Connection: keep-alive Header with the response, So the client keeps the connection alive. So the client won't keep the connection alive till your server response.
So in your first scenario, The server replies with the header as soon as the request received. So the second response (actually may reuse, you got lucky since the server respond your request, before it sends the second one) reuses the TCP connection.
But in second scenario, server waits 2 seconds to send the response, So the client won't know it should be a keep alive connection till next 2 seconds. But all other requests need to be sent before that, so as default it will create a new connection for each HTTP Request.
This might be efficient if you need to continuously call HTTP interface, like req -> res -> req -> res, But also this might be inefficient if you want to get independent collection of data from the server.
Try this on client side if you have any doubts,
setTimeout(() => {
fetch('' + i).then(data => console.log(data));
setTimeout(function () {
for (let i = 2; i < 11; i++) {
fetch('' + i).then(data => console.log(data));
}
}, 5000)
}, 10000);

Related

Is it possible to send second request over persistent connection, before response is returned. Node.js

I know it sound strange but the situation is strange. I have been trying to send second request to the server(over the same connection) before the first response. What I get is response to the first request and the second one is ignored by the server. The server keeps the connection alive:
server.on("connection", socket => {
socket.setTimeout(50 * 1000);
socket.on("timeout", () => {
console.log("socket timeout");
socket.end();
});
socket.on("close", hadError => {
console.log(
hadError
? "Socket closed due to ERROR during transmission"
: "Socket closed"
);
});
});
This is the route that handles the request:
app.post("/enddev", (req, res) => {
console.log("REQUEST TO ", req.route.path);
console.log("Req Header ", req.headers);
res.write("request\r\n");
setInterval(() => {
res.write("ack\r\n");
}, 2000);
});
I think that the HTTP protocol does not allow this, but may be there is some workaround.
Websockets are not solutions because the client is (not a browser) an embedded device.
And here is an image of the test (I use a TCP client terminal program ti simulate the request message from the client):
The right terminal (black one) is the node server. The white one is the terminal. On the terminal in pink is is what the client sent. In black is the response from the server. You can see that the server sends "request" message right after the header. But upon second request no response appears.
I don't think you're ever calling res.end(). Without that, node.js thinks your response is not over yet.

When tunnelling a TLS connection, how to pass additional information?

I have created a bare-bones HTTP proxy that performs HTTP tunnelling using HTTP CONNECT method.
const http = require('http');
const https = require('https');
const pem = require('pem');
const net = require('net');
const util = require('util');
const createHttpsServer = (callback) => {
pem.createCertificate({
days: 365,
selfSigned: true
}, (error, {serviceKey, certificate, csr}) => {
const httpsOptions = {
ca: csr,
cert: certificate,
key: serviceKey
};
const server = https.createServer(httpsOptions, (req, res) => {
// How do I know I here whats the target server port?
res.writeHead(200);
res.end('OK');
});
server.listen((error) => {
if (error) {
console.error(error);
} else {
callback(null, server.address().port);
}
});
});
};
const createProxy = (httpsServerPort) => {
const proxy = http.createServer();
proxy.on('connect', (request, requestSocket, head) => {
// Here I know whats the target server PORT.
const targetServerPort = Number(request.url.split(':')[1]);
console.log('target server port', targetServerPort);
const serverSocket = net.connect(httpsServerPort, 'localhost', () => {
requestSocket.write(
'HTTP/1.1 200 Connection established\r\n\r\n'
);
serverSocket.write(head);
serverSocket.pipe(requestSocket);
requestSocket.pipe(serverSocket);
});
});
proxy.listen(9000);
};
const main = () => {
createHttpsServer((error, httpsServerPort) => {
if (error) {
console.error(error);
} else {
createProxy(httpsServerPort);
}
});
};
main();
The server accepts a HTTPS connection and responds with "OK" message without forwarding the request further.
As you can see in the code (see // Here I know whats the target server PORT.), I can obtain the target server's port within the HTTP CONNECT event handler. However, I am unable to figure out how to pass this information to the createHttpsServer HTTP server router (see // How do I know I here whats the target server port?).
When tunnelling a TLS connection, how to pass additional information?
The above code can be tested by running:
$ node proxy.js &
$ curl --proxy http://localhost:9000 https://localhost:59194/foo.html -k
The objective is to respond with "OK localhost:59194".
You can't add anything to a TLS stream (thankfully), short of tunneling it inside another protocol⁠—which is what the Connect method already does. But, since you have the HTTP proxy and the HTTPS server in the same codebase, you don't need to fling the TLS stream over the network another time. Instead, you want to parse the TLS stream, and then you can pass any variables to the code that handles it.
However, after parsing TLS you'll still have a raw HTTP stream, and you'll need an HTTP server to turn it into requests and to handle responses.
The quick and rather dirty way to go about it is to use Node's HTTPS server to both decode TLS and parse HTTP. But the server's API doesn't provide for dealing with sockets that are already connected, and server's code isn't cleanly separated from connection code. So you need to hijack the server's internal connection-handling logic—this is of course susceptible to breakage in case of future changes:
const http = require('http');
const https = require('https');
const pem = require('pem');
const createProxy = (httpsOptions) => {
const proxy = http.createServer();
proxy.on('connect', (request, requestSocket, head) => {
const server = https.createServer(httpsOptions, (req, res) => {
res.writeHead(200);
res.end('OK');
});
server.emit('connection', requestSocket);
requestSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
});
proxy.listen(9000);
};
const main = () => {
pem.createCertificate({
days: 365,
selfSigned: true
}, (error, {serviceKey, certificate, csr}) => {
createProxy({
ca: csr,
cert: certificate,
key: serviceKey
});
});
};
main();
To avoid creating an HTTPS server instance on every request, you can move the instance out and tack you data onto the socket object instead:
const server = https.createServer(httpsOptions, (req, res) => {
res.writeHead(200);
// here we reach to the net.Socket instance saved on the tls.TLSSocket object,
// for extra dirtiness
res.end('OK ' + req.socket._parent.marker + '\n');
});
proxy.on('connect', (request, requestSocket, head) => {
requestSocket.marker = Math.random();
server.emit('connection', requestSocket);
requestSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
});
With the above code, if you do several successive requests:
curl --proxy http://localhost:9000 https://localhost:59194/foo.html \
https://localhost:59194/foo.html https://localhost:59194/foo.html \
https://localhost:59194/foo.html https://localhost:59194/foo.html -k
then you'll also notice that they're processed on a single connection, which is nice:
OK 0.6113572936982015
OK 0.6113572936982015
OK 0.6113572936982015
OK 0.6113572936982015
OK 0.6113572936982015
I can't quite vouch that nothing will be broken by handing the socket to the HTTPS server while the proxy server already manages it. [The server has the presence of mind to not overwrite another instance on the socket object](https://github.com/nodejs/node/blob/v10.9.0/lib/_http_server.js#L331), but otherwise seems to be rather closely involved with the socket. You'll want to test it with longer-running connections.
As for the `head` argument, [which can indeed contain initial data](https://www.rfc-editor.org/rfc/rfc2817#section-5.2):
you might be able to put it back on the stream with requestSocket.unshift(head), but I'm not sure that it won't be immediately consumed by the proxy server.
Or, you might be able to chuck it over to the HTTPS server with requestSocket.emit('data', head) since the HTTP server seems to use the stream events, however TLS socket source calls read() for whatever reason, and that's mutually exclusive with the events, so I'm not sure how they even work with each other.
One solution would be to make your own wrapper for stream.Duplex that will forward all calls and events, except for read() in the case when this initial buffer exists—and then use this wrapper in place of requestSocket. But you'll then need to replicate the 'data' event also, in accordance with the logic of Node's readable streams.
Finally, you can try creating a new duplex stream, write head and pipe the socket to it, like you did initially, and use the stream in place of the socket for the HTTPS server—not sure that it will be compatible with HTTP server's rather overbearing management of the socket.
An cleaner approach is to decode the TLS stream and use a standalone parser for the resultant HTTP stream. Thankfully, Node has a tls module that is nicely isolated and turns TLS sockets into regular sockets:
proxy.on('connect', (request, requestSocket, head) => {
const httpSocket = new tls.TLSSocket(requestSocket, {
isServer: true,
// this var can be reused for all requests,
// as it's normally saved on an HTTPS server instance
secureContext: tls.createSecureContext(httpsOptions)
});
...
});
See caveats on tls.createSecureContext regarding replicating the behavior of the HTTPS server.
Alas, Node's HTTP parser isn't so usable: it's a C library, which necessitates quite a bit of legwork between the socket and the parser calls. And the API can (and does) change between versions, without warnings, with a larger surface for incompatibilities compared to the HTTP server internals used above.
There are NPM modules for parsing HTTP: e.g. one, two, but none seem too mature and maintained.
I also have doubts about the feasibility of a custom HTTP server because network sockets tend to require plenty of nurture over time due to edge cases, with hard-to-debug timeout issues and such—which should all be already accounted for in the Node's HTTP server.
P.S. One possible area of investigation is how the Cluster module handles connections: afaik the parent process in a cluster hands connection sockets over to the children, but it doesn't fork on every request—which suggests that the child processes somehow deal with connected sockets, in code that's outside of an HTTP server instance. However, since the Cluster module is now in the core, it may exploit non-public APIs.

Pooled connections - monitoring concurrent requests in Node.js Request library?

I'm sending out a lot of requests to another server, and want to limit them so as to not overload the server. My impression is that this can be done with the pool parameter in options, but I'm not sure if I'm doing so properly.
I'd like to be able to keep track of when the requests are sent out, as I'm trying to establish a duplex connection, and need to make sure the corresponding GET and POST requests are sent out at the same time.
Here's a simplified example of what I'm trying:
var request = require('request');
var options = {
'url': 'http://www.google.com',
'pool': {
'maxSockets': 3
}
};
for (var i = 0; i < 100; i++) {
request.get(options, (function(j) {
return function(err, res, body) {
console.log(j);
}
})(i));
}
Is there an event emitted when the requests are actually sent out? Is there any way for me to track when, and in what order each request is being sent out?
I found this in the Node.js documentation:
Event: 'socket'#
function (socket) { }
Emitted after a socket is assigned to this request.
You can use it to monitor connections as they are assigned sockets like this:
http.get(options, function(res) {
// Do stuff
}).on("socket", function (socket) {
socket.on("connection") {
// record connection
};
});
The same event is emitted with the request library, since it's just a wrapper around the builtin http module.

Can't close server (nodeJS)

Why I can't close the server by requesting localhost:13777/close in browser (it continues to accept new requests), but it will gracefully close on timeout 15000? Node version is 0.10.18. I fell into this problem, trying to use code example from docs on exceptions handling by domains (it was giving me 'Not running' error every time I secondly tried to request error page) and finally came to this code.
var server
server = require("http").createServer(function(req,res){
if(req.url == "/close")
{
console.log("Closing server (no timeout)")
setTimeout(function(){
console.log("I'm the timeout")
}, 5000);
server.close(function(){
console.log("Server closed (no timeout)")
})
res.end('closed');
}
else
{
res.end('ok');
}
});
server.listen(13777,function(){console.log("Server listening")});
setTimeout(function(){
console.log("Closing server (timeout 15000)")
server.close(function(){console.log("Server closed (timeout 15000)")})
}, 15000);
The server is still waiting on requests from the client. The client is utilizing HTTP keep-alive.
I think you will find that while the existing client can make new requests (as the connection is already established), other clients won't be able to.
Nodejs doesn't implement a complex service layer on top of http.Server. By calling server.close() you are instructing the server to no longer accept any "new" connections. When a HTTP Connection:keep-alive is issued the server will keep the socket open until the client terminates or the timeout is reached. Additional clients will not be able to issue requests
The timeout can be changed using server.setTimeout() https://nodejs.org/api/http.html#http_server_settimeout_msecs_callback
Remember if a client has created a connection before the close event that connection can continually be used.
It seems that a lot of people do not like this current functionality but this issue has been open for quite a while:
https://github.com/nodejs/node/issues/2642
As the other answers point out, connections may persist indefinitely and the call to server.close() will not truly terminate the server if any such connections exist.
We can write a simple wrapper function which attaches a destroy method to a given server that terminates all connections, and closes the server (thereby ensuring that the server ends nearly immediately!)
Given code like this:
let server = http.createServer((req, res) => {
// ...
});
later(() => server.close()); // Fails to reliably close the server!
We can define destroyableServer and use the following:
let destroyableServer = server => {
// Track all connections so that we can end them if we want to destroy `server`
let sockets = new Set();
server.on('connection', socket => {
sockets.add(socket);
socket.once('close', () => sockets.delete(socket)); // Stop tracking closed sockets
});
server.destroy = () => {
for (let socket of sockets) socket.destroy();
sockets.clear();
return new Promise((rsv, rjc) => server.close(err => err ? rjc(err) : rsv()));
};
return server;
};
let server = destroyableServer(http.createServer((req, res) => {
// ...
}));
later(() => server.destroy()); // Reliably closes the server almost immediately!
Note the overhead of entering every unique socket object into a Set

Node.js WebSocket Broadcast

I'm using the ws library for WebSockets in Node.js and
I'm trying this example from the library examples:
var sys = require("sys"),
ws = require("./ws");
ws.createServer(function (websocket) {
websocket.addListener("connect", function (resource) {
// emitted after handshake
sys.debug("connect: " + resource);
// server closes connection after 10s, will also get "close" event
setTimeout(websocket.end, 10 * 1000);
}).addListener("data", function (data) {
// handle incoming data
sys.debug(data);
// send data to client
websocket.write("Thanks!");
}).addListener("close", function () {
// emitted when server or client closes connection
sys.debug("close");
});
}).listen(8080);
All OK. It works, but running 3 clients, for instance, and sending "Hello!" from one will make the server only reply "Thanks!" to the client which sent the message, not to all.
How can I broadcast "Thanks!" to all connected clients when someone sends "Hello!"?
Thanks!
If you want to send out to all clients, you have to keep track of them. Here is a sample:
var sys = require("sys"),
ws = require("./ws");
// # Keep track of all our clients
var clients = [];
ws.createServer(function (websocket) {
websocket.addListener("connect", function (resource) {
// emitted after handshake
sys.debug("connect: " + resource);
// # Add to our list of clients
clients.push(websocket);
// server closes connection after 10s, will also get "close" event
// setTimeout(websocket.end, 10 * 1000);
}).addListener("data", function (data) {
// handle incoming data
sys.debug(data);
// send data to client
// # Write out to all our clients
for(var i = 0; i < clients.length; i++) {
clients[i].write("Thanks!");
}
}).addListener("close", function () {
// emitted when server or client closes connection
sys.debug("close");
for(var i = 0; i < clients.length; i++) {
// # Remove from our connections list so we don't send
// # to a dead socket
if(clients[i] == websocket) {
clients.splice(i);
break;
}
}
});
}).listen(8080);
I was able to get it to broadcast to all clients, but it's not heavily tested for all cases. The general concept should get you started though.
EDIT: By the way I'm not sure what the 10 second close is for so I've commented it out. It's rather useless if you're trying to broadcast to all clients since they'll just keep getting disconnected.
I would recommend you to use socket.io. It has example web-chat functionality out of the box and also provides abstraction layer from the socket technology on client (WebSockets are supported by Safari, Chrome, Opera and Firefox, but disabled in Firefox and Opera now due to security vulnerabilities in ws-protocol).

Categories

Resources