如何绕过之前在 Nodebb 中声明的 json body-parser?

How to get around previously declared json body-parser in Nodebb?

我正在为nodebb(开放论坛软件)写一个私人插件。在 nodebb 的 webserver.js 文件中,有一行似乎占用了所有传入的 json 数据。

app.use(bodyParser.json(jsonOpts));

我正在尝试将我的一个端点的所有传入 json 数据转换为原始数据。然而,挑战是我无法删除或修改上面的行。

仅当我暂时删除上面的行时,以下代码才有效。

    var rawBodySaver = function (req, res, buf, encoding) {
      if (buf && buf.length) {
        req.rawBody = buf.toString(encoding || 'utf8');
      }
    }

    app.use(bodyParser.json({ verify: rawBodySaver }));

但是,一旦我将 app.use(bodyParser.json(jsonOpts)); 中间件放回 webserver.js 文件中,它就会停止工作。所以看起来 body-parser 只处理第一个匹配传入数据类型的解析器,然后跳过所有其余的解析器?

我该如何解决这个问题?我在他们的官方文档中找不到任何信息。

非常感谢任何帮助。

** 更新 **

我要解决的问题是正确处理传入的条带 webhook 事件。在官方条纹文档中,他们建议我执行以下操作:

  // Match the raw body to content type application/json
  app.post('/webhook', bodyParser.raw({type: 'application/json'}), 
  (request, response) => {
    const sig = request.headers['stripe-signature'];

    let event;

    try {
      event = stripe.webhooks.constructEvent(request.body, sig, 
  endpointSecret);
    } catch (err) {
      return response.status(400).send(Webhook Error: 
  ${err.message});
    }

这两种方法,这个 post 顶部的原始方法和 stripe 官方推荐的方法,都可以正确构造 stripe 事件,但前提是我删除了网络服务器中的中间件。所以我现在的理解是你不能有多个中间件来处理相同的传入数据。对于第一个中间件,我没有太多回旋余地,除了能够修改传递给它并来自 .json 文件的参数 (jsonOpts)。我尝试添加一个验证字段,但我不知道如何添加一个函数作为它的值。我希望这是有道理的,很抱歉没有说明我最初要解决的问题。

bodyParser.json() 中间件执行以下操作:

  1. 检查传入请求的响应类型是否为 application/json
  2. 如果是那种类型,则从传入流中读取主体以从流中获取所有数据。
  3. 当它拥有流中的所有数据时,将其解析为 JSON 并将结果放入 req.body 以便后续请求处理程序可以访问已读取和已解析的数据那里。

因为是从流中读取数据,流中已经没有数据了。除非它把原始数据保存在某个地方(我还没看是否保存了),否则原始的 RAW 数据就消失了——它已经从流中读取过了。这就是为什么你不能让多个不同的中间件都试图处理同一个请求体。无论哪一个先读取传入流中的数据,然后流中不再存在原始数据。

为了帮助您找到解决方案,我们需要知道您真正要解决的最终问题是什么?您将无法让两个中间件都在寻找相同的内容类型并同时读取请求正文。您可以替换 bodyParser.json(),它既可以执行现在的操作,也可以在同一个中间件中为您的目的执行其他操作,但不能在单独的中间件中执行。

我在不修改 NodeBB 代码的情况下找到的唯一解决方案是将你的中间件插入一个方便的钩子中(这会比你想要的晚),然后侵入应用程序路由器中的层列表以更早地移动该中间件在应用程序层列表中将其放在您想要放在前面的东西前面。

这是一个 hack,所以如果 Express 在未来的某个时候改变了他们的内部实现,那么这可能会崩溃。但是,如果他们改变了实现的这一部分,它可能只是一个主要的修订(如 Express 4 ==> Express 5)并且你可以调整代码以适应新方案或者 NodeBB 可能会给出到时候给你一个合适的钩子。

基本概念如下:

  1. 获取您需要修改的路由器。它似乎是您想要用于 NodeBB 的 app 路由器。
  2. 像往常一样插入您的 middleware/route 以允许 Express 为您的 middleware/route 进行所有正常设置,并将其插入应用程序路由器的内部层列表中。
  3. 然后,进入列表,将其从列表的末尾(刚刚添加的位置)取出并插入到列表的前面。
  4. 找出将其放在列表前面的位置。您可能不希望它位于列表的最开头,因为那样会将它放在一些有用的系统中间件之后,这些中间件可以使查询参数解析等工作正常进行。因此,代码会从我们知道的内置名称中寻找第一个名称我们无法识别的中间件,然后立即将其插入。

这是插入中间件的函数代码。

function getAppRouter(app) {
    // History:
    //   Express 4.x throws when accessing app.router and the router is on app._router
    //      But, the router is lazy initialized with app.lazyrouter()
    //   Express 5.x again supports app.router 
    //      And, it handles the lazy construction of the router for you
    let router;
    try {
        router = app.router;      // Works for Express 5.x, Express 4.x will throw when accessing
    } catch(e) {}
    if (!router) {
        // Express 4.x
        if (typeof app.lazyrouter === "function") {
            // make sure router has been created
            app.lazyrouter();
        }
        router = app._router;
    }

    if (!router) {
        throw new Error("Couldn't find app router");
    }
    return router;
} 

// insert a method on the app router near the front of the list
function insertAppMethod(app, method, path, fn) {
    let router = getAppRouter(app);
    let stack = router.stack;

    // allow function to be called with no path
    // as insertAppMethod(app, metod, fn);
    if (typeof path === "function") {
        fn = path;
        path = null;
    }

    // add the handler to the end of the list
    if (path) {
        app[method](path, fn);
    } else {
        app[method](fn);
    }
    // now remove it from the stack
    let layerObj = stack.pop();
    // now insert it near the front of the stack, 
    // but after a couple pre-built middleware's installed by Express itself
    let skips = new Set(["query", "expressInit"]);
    for (let i = 0; i < stack.length; i++) {
        if (!skips.has(stack[i].name)) {
            // insert it here before this item
            stack.splice(i, 0, layerObj);
            break;
        }
    }
}

然后您可以使用它从任何在启动期间为您提供 app 对象的 NodeBB 挂钩插入您的方法。它将创建您的 /webhook 路由处理程序,然后将其更早地插入到层列表中(在其他主体解析器中间件之前)。

let rawMiddleware = bodyParser.raw({type: 'application/json'});

insertAppMethod(app, 'post', '/webhook', (request, response, next) => {
    rawMiddleware(request, response, (err) => {
        if (err) {
            next(err);
            return;
        }
        const sig = request.headers['stripe-signature'];

        let event;

        try {
          event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
          // you need to either call next() or send a response here
        } catch (err) {
          return response.status(400).send(`Webhook Error: ${err.message}`);
        }
    });
});