简体   繁体   中英

What is the proper way to release a socket after an ERR_STREAM_PREMATURE_CLOSE error occured in Node.js?

I have the problem that when I use a custom http agent, sockets are not released when an error occurs during the piping of the response.

It seems that Node.js isn't doing this on it's own. Thats why I tried to release it myself by destroying the response and the underlying socket but it didn't work. Nothing I tried worked expect destroying everything with agent.destroy() . This obviously can't be the solution because like this properly running sockets are getting destroyed.

That leads me to my question. What is the proper way to release a socket?

Following an example to reproduce it.

  • Run the script below with node index.js
  • Open http://localhost:3000/
  • Cancel download

在 chrome 中取消下载

  • You will see the socket remains occupied forever.

套接字未释放

If you call http://localhost:3000/small you see the behaviour I would expect to happen. Which is that the socket is getting released.

正在释放的套接字

 const https = require("https"); const http = require("http"); const stream = require("stream"); const agent = new https.Agent({ maxSockets: 20, maxFreeSockets: 10, keepAlive: true, keepAliveMsecs: 5000 }); const server = http.createServer((req, res) => { let url = "https://releases.ubuntu.com/20.04.3/ubuntu-20.04.3-live-server-amd64.iso?_ga=2.138549238.47332115.1635229845-1229485524.1607530765"; if (req.url === "/small") { url = "https://nodejs.org/dist/v14.18.1/node-v14.18.1-x64.msi"; } https.get(url, { agent }, (stream) => { stream.pipe(res); }).on("error", (e) => { console.error("Got error: " + e.message); }); const cleanup = stream.finished(res, (error) => { if (error) { if (error.code === "ERR_STREAM_PREMATURE_CLOSE") { console.error("Pipeline ended non gracefully with no explicit error"); // agent.destroy(); -- Don't want to do this! res.socket.destroy(); res.destroy(); } } else { console.info("Stream succeeded."); } cleanup(); }); logSockets(); }); const getSocketCountPerHost = (socketGroup) => { const regexp = /^:+|:+$/g; const sockets = agent[socketGroup]; return Object.fromEntries(Object.entries(sockets).map(([key, value]) => [key.replace(regexp, ""), Array.isArray(value) ? value.length : value])); } const logSockets = () => { const sockets = getSocketCountPerHost("sockets"); const freeSockets = getSocketCountPerHost("freeSockets"); console.info("httpsAgent.sockets", sockets); console.info("httpsAgent.freeSockets", freeSockets); }; server.listen(3000, () => { console.log("Listening on port 3000"); setInterval(logSockets, 10_000) });

Prerequisites:

Ok, got it myself. The key is to get the request from the https.Agent and destroy it if it gets closed prematurely.

 const clientRequest = https.get(url, { agent }, (stream) => { stream.pipe(res); }).on("error", (e) => { console.error("Got error: " + e.message); }); const cleanup = stream.finished(res, (error) => { if (error) { if (error.code === "ERR_STREAM_PREMATURE_CLOSE") { console.error("Pipeline ended non gracefully with no explicit error"); clientRequest.destroy(); } } else { console.info("Stream succeeded."); } cleanup(); });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM