首页 > 解决方案 > 在 Node.js 中发生 ERR_STREAM_PREMATURE_CLOSE 错误后释放套接字的正确方法是什么?

问题描述

我的问题是,当我使用自定义 http 代理时,在响应管道期间发生错误时不会释放套接字。

似乎 Node.js 并没有自己做这件事。这就是为什么我试图通过破坏响应和底层套接字来自己释放它,但它不起作用。我尝试过的任何操作都没有预期用agent.destroy(). 这显然不是解决方案,因为像这样正常运行的套接字正在被破坏。

这引出了我的问题。释放套接字的正确方法是什么?

下面通过一个例子来重现它。

在chrome中取消下载

套接字未释放

如果你调用 http://localhost:3000/small 你会看到我期望发生的行为。那就是套接字正在被释放。

正在释放的套接字

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)
});

先决条件:

标签: node.js

解决方案


好的,我自己搞定了。关键是从 获取请求https.Agent并在它过早关闭时将其销毁。

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();
});


推荐阅读