首页 > 解决方案 > router.get 发送 res 两次

问题描述

所以我用我的脚本缩小了问题的范围。

问题是这条线

clients.push(res);

它似乎在做的是 2 / 3 次而不是 1 次。

在 console.log 它显示:

STATION_ID Completed
754542
get data connected!
undefined
STATION_ID Stream END
get POdcast ran
STATION_ID Completed
754542
get data connected!
undefined
STATION_ID Stream END
get POdcast ran

但是,当我从中删除 res 时,clients.push(res);它会正常触发,但不会在浏览器中返回给客户端。

有什么建议么?

完整代码:

    router.get('/track/:url(*)', (req, res) =>{
    var url = req.params.url.substr(0); 
    console.log('/track/'+url);

    var length = 0;
    var e = 0;
    /* AD SYS */
    var remote = "https://storage.googleapis.com/ad-system/testfolder/OUTOFAREA.mp3";  
    var adsys = needle.get(remote)
    /* PODCAST */
    var filesize = needle.get(url, function(error) {
       if(error){
        e = 505;
        res.send("<html><head></head><body>NOPE</body></html>");
        console.log(error)
        //filesize.end();
        res.end();

       }
    });


        adsys.on('response', function(resB) {
            console.log("STATION_ID Completed");
            length =  Number(resB.headers['content-length']);
           // console.log(length);           
        });

        filesize.on('response', function(resC) {
                console.error("get data connected!");
                console.log(resC.headers['content-length']);
                a = Number(resC.headers['content-length']);
                length = length+a;

      });
      res.set({
        "Content-Type": "audio/mpeg",
        'Transfer-Encoding': 'chunk',
    //    'Content-Disposition': 'attachment',
    //    'Content-Length':length
    });

      adsys.on("finish", function() {

        console.log(" X STATION_ID Stream END");

           getPodcast();



    });  


    adsys.on("data", function (chunk) {
            // console.log(clients.length);
           /* if (clients.length > 0){
                for (client in clients){
                    clients[client].write(chunk);
                    //console.log(chunk);
                };
            }*/
            res.write(chunk);

    });

    function getPodcast(){


        filesize.on("data", function (chunk) {
           res.write(chunk);
        });

        filesize.on('end', function(){
            console.log("end");
            res.end();
        });
    }
});

module.exports = router;

标签: node.jsexpressrouter

解决方案


好的,现在您拥有更多完整代码,因此我可以看到您要完成的工作。

我看到的几个问题:

  1. 该变量a未定义,因此它是一个隐式全局变量。那很糟。在本地某处声明它。
  2. 在计算组合内容长度时,您的两个请求之间存在完整的竞争条件。您的代码假设adsys为您提供了response第一个,但这并不保证会发生。
  3. 您计算该长度,但实际上并未使用它。你不能把它放在你的res.set()指令中,因为当它运行时你还没有完成计算。
  4. 似乎您至少缺少对 adsys 请求的错误处理。
  5. 在我看来,在 needle 文档中,needle 请求的完成事件是"done",不是"finish"

这是一个经过显着清理的代码版本,其中包含一堆新的日志记录以帮助调试:

let trackCntr = 0;
function getLogger() {
    let id = ++trackCntr;
    return function(...args) {
        args.unshift(`(${id}): `);
        console.log(...args);
    }
}

router.get('/track/:url(*)', (req, res) => {
    const log = getLogger();
    const url = req.params.url.substr(0);
    log(`(${trackCntr}) /track/${url}`);

    /* AD SYS */
    const remote = "https://storage.googleapis.com/ad-system/testfolder/OUTOFAREA.mp3";
    const adsys = needle.get(remote);

    /* PODCAST */
    const filesize = needle.get(url);

    let responseCntr = 0;
    let length = 0;
    let errReportedAlready = false;

    adsys.on("response", function(resB) {
        log(`adsys.on('response'), content-length=${resB.headers['content-length']}`);
        length += +resB.headers['content-length'];
        ++responseCntr;
        checkResponseCntr();
    });

    adsys.on("err", sendErr);
    adsys.on("timeout", sendErr);

    filesize.on("response", function(resC) {
        log(`filesize.on('response'), content-length=${resC.headers['content-length']}`);
        length += +resC.headers['content-length'];
        ++responseCntr;
        checkResponseCntr();
    });

    filesize.on("err", sendErr);
    filesize.on("timeout", sendErr);

    // this is called if either needle requests gets an error
    function sendErr(err) {
        log("sendErr", err);
        if (!errReportedAlready) {
            errReportedAlready = true;
            if (res.headersSent) {
                // just need to abort the response because headers have already been sent
                res.end();
            } else {
                // send error status
                res.sendStatus(500);
            }
        }
    }


    // code continues here after both response headers above have completed
    function checkResponseCntr() {
        log(`checkResponseCntr(${responseCntr})`)
        // if we have received both responses, then start streaming ad data
        if (responseCntr === 2) {
            log("got both responses");
            res.set({
                "Content-Type": "audio/mpeg",
                "Transfer-Encoding": "chunk",
                "Content-Length": length
            });

            // start streaming ad data
            getAd();

        }
    }

    function getAd() {
        log("getAd()");

        // this will cause adsys data to start flowing
        adsys.on("data", function(chunk) {
            if (!errReportedAlready) {
                res.write(chunk);
            }
        });

        adsys.on("done", function() {
            log("adsys done");
            // now trigger getting the podcast data
            getPodcast();
        });
    }

    function getPodcast() {
        log("getPodcast()");
        filesize.on("data", function(chunk) {
            if (!errReportedAlready) {
                res.write(chunk);
            }
        });

        filesize.on("done", function() {
            log("filesize done");
            if (!errReportedAlready) {
                log("res.end()")
                res.end();
            }
        });
    }
});

module.exports = router;

这将执行以下操作:

  1. 正确计算长度而不考虑两个请求的竞争条件顺序。
  2. needle()为这两个请求添加错误处理。
  3. 添加正确的content-length标题设置。
  4. 根据文档更改对完成的监视needle()以使用"done"事件。needle()
  5. 代码getAd()getPodcast()类似的。

可能的问题仍然:

  1. 如果流式传输广告需要很长时间,我可以想象您的文件大小请求超时。

我可以在我自己的小 nodejs 应用程序中运行上面的代码,这是我得到的日志:

(1):  (1) /track/https://storage.googleapis.com/radiomediapodcast/wellwellnow/season1/S01E04.mp3
(1):  adsys.on('response'), content-length=754542
(1):  checkResponseCntr(1)
(1):  filesize.on('response'), content-length=63062853
(1):  checkResponseCntr(2)
(1):  got both responses
(1):  getAd()
(1):  adsys done
(1):  getPodcast()
(2):  (2) /track/https://storage.googleapis.com/radiomediapodcast/wellwellnow/season1/S01E04.mp3
(2):  adsys.on('response'), content-length=754542
(2):  checkResponseCntr(1)
(2):  filesize.on('response'), content-length=63062853
(2):  checkResponseCntr(2)
(2):  got both responses
(2):  getAd()
(2):  adsys done
(2):  getPodcast()
(2):  filesize done
(2):  res.end()
(1):  filesize done
(1):  res.end()

您可以清楚地看到两个单独的请求进入。第二个请求在第一个请求发送它的标头后立即到达 - 不知道为什么。


我已经确定双重请求是由将音频 URL 放入 Chrome URL 栏中引起的。如果我将该 URL 放入 HTML 页面中的音频标签中,那么我们将不再收到双重请求。我创建了这个简单的 HTML 页面:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
</head>
<body>
    <figure>
        <figcaption>Listen to the T-Rex:</figcaption>
        <audio
            controls
            src="http://localhost/track/https%3A%2F%2Fstorage.googleapis.com%2Fradiomediapodcast%2Fwellwellnow%2Fseason1%2FS01E04.mp3">
                Your browser does not support the
                <code>audio</code> element.
        </audio>
    </figure>
</body>
</html>

然后,我得到了这个日志(对我来说看起来很正确):

(1):  (1) /track/https://storage.googleapis.com/radiomediapodcast/wellwellnow/season1/S01E04.mp3
(1):  Accept: */*
(1):  adsys.on('response'), content-length=754542
(1):  checkResponseCntr(1)
(1):  filesize.on('response'), content-length=63062853
(1):  checkResponseCntr(2)
(1):  got both responses
(1):  getAd()
(1):  adsys done
(1):  getPodcast()
(1):  filesize done
(1):  res.end()

而且,双重请求似乎是 Chrome/Edge URL 栏的事情,因为当我将 URL 放入 Firefox URL 栏中时,它不会在 Firefox 中发生。


推荐阅读