0

我正在为 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});
    }

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

4

2 回答 2

0

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

这是一个 hack,因此如果 Express 在未来某个时间更改其内部实现,那么这可能会中断。但是,如果他们改变了这部分实现,它可能只会在一个主要版本中(如在 Express 4 ==> Express 5 中),您可以调整代码以适应新方案,或者 NodeBB 可能会给出到那时你是一个合适的钩子。

基本概念如下:

  1. 获取您需要修改的路由器。看来它是app您想要的 NodeBB 路由器。
  2. 像往常一样插入您的中间件/路由,以允许 Express 为您的中间件/路由进行所有正常设置,并将其插入应用程序路由器的内部层列表中。
  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;
        }
    }
}

然后,您将使用它从任何 NodeBB 钩子中插入您的方法,该钩子app在启动期间的某个时间为您提供对象。它将创建您的/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}`);
        }
    });
});
于 2020-02-05T18:56:42.567 回答
0

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

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

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

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

于 2020-02-05T01:47:53.503 回答