记一次 Node debug 过程

hello~亲爱的看官老爷们大家好~最近接手维护公司另一个 Node 项目,稍微熟悉一下代码后,便被提了一个解决线上 bug 的需求。定位问题后解决还是十分容易的,但是这个过程十分有趣,bug 出现的原因也值得深思。因而有了这篇文章,分享这次 bug fixes 的过程。

背景

项目是搜索相关的,页端发请求到 Node 端,Node 包装搜索请求参数后转发 JavaJava 返回数据后,Node 将数据组装成 HTML 文档返回给页端。整个过程非常常规,但 Bug 出现的位置就十分奇妙了,线上偶现渲染出来的 HTML 文档中部分 a 标签链接指向了错误但有意义的地址。简单的代码如下:

const axios = require('axios');
const _arr = [
  //数组内是一个个对象,保存着一些公用的数据
];

router.get('/test', (req, res) => {
  axios(
    //请求 Java 的各种配置
  )
    .then(response => {
      //对 response 进行处理,省略若干步骤
      for (let item of _arr) {
        item.url = `?${querystring.stringify(response.url)}`;
      }
    })
    .then(() => {
      //其他的异步操作
    })
    .then(data => {
      res.render({
        //...各种数据
        _arr
      })
    })

})

module.exports = router;

如果对 Node 模块机制比较熟悉的同学,应该能看出问题的根源了。但如果是接手的项目,而且实际代码比这个复杂 N 倍(项目中此接口大约有700行代码),估计就没那么好分析了。

问题分析

当时我接手时,在本地环境和测试环境,无法复现出相同的问题。同时,被修改的链接其实是重新搜索的链接,如之前搜索的是 123,跳转的链接正常来说还是 123 的,但会变为 xxx风景区。因而一度认为是有中间人进行攻击,修改了模板中的链接。但分析的过程中,发现两个不合理的地方:

  • 如果真的是中间人攻击,那么为何不修改全部的链接?
  • 搜索页面支持 Https,修改修改协议后,线上环境仍偶现该问题。

如果说第一点还情有可原的话,第二点基本就认定不是中间人的问题,除非中间人强到破解了加密或公司的私钥泄密了。于是问题又回到了原点,代码到底哪里又出问题了呢?

线上环境是较难调试的,但可以通过打日志的方式进行分析。通过打点,发现请求 Java 后返回的 response.url 是正确的,但在渲染时 _arrurl 却不同了,可以确定问题是出在 _arr 上,因为某些原因,它的值被修改了。

定位到具体出现问题的地方后,原因归结起来就很简单了,Node 中的模块机制,其实是通过函数包装而成的,在此过程中会形成闭包,简略示例如下:

const module = {}; //全局变量

function(exports, require, module, __fileName, __dirname) {
  const _arr = [];
  module.exports = ....;
}

每个文件都会被加工成上面代码的形式,而 module 是全局可访问的。而且需要记住一点,不是每次用到某个文件时,函数都会执行一次,而是只要该文件被 require 一次后,它的相关代码已经被记录在全局的 module 中了。因而 _arr 是以单例的形式存在的,如果 exports 出去的代码对 _arr 做了修改的话,会影响此后对 _arr 的读取。好像有点绕,再简单一点说,如一下 demo

function wrap() {
  const arr = [];
  return function() {
    console.log(arr);
    arr.push(1);
  }
}

const module = wrap();

module(); //[]
module(); //[1]
module(); //[1, 1]

而项目的代码有这么一段:

for (let item of _arr) {
    item.url = `?${querystring.stringify(response.url)}`;
}

这段代码是有副作用的,它修改了 _arr 的内容,如果之后都是同步的代码,那么问题可能还不大(其实还是有问题,但不会影响结果),但只要是异步的,那么问题可能就会出现。知道了问题的根源,解决就很简单了,要么每次调用都重新定义 _arr 一次,要么将代码改为无副作用的~

小结

以上就是整个 debug 过程的简要描述,其实只是之前开发的同学某些细节没有考虑到,还好访问量不大,但造成的后果不算特别严重。然而分析下来,发现这个 bug 是若干 Js 基础问题共同作用而成的,当我们不断追求新技术、新框架的同时,是不是该静下心来,好好巩固一下语言的基础呢?

感谢各位看官大人看到这里,知易行难,希望本文对你有所帮助~谢谢!