编写Express中间件以在使用body-parser之前获取原始请求正文

7
我编写了一个 Express 中间件,用于从请求中提取原始正文,并在 body-parser 中间件之前设置它。
我的自定义中间件调用 req.setEncoding('utf8'),但这会导致以下 body-parser 错误:

Error: stream encoding should not be set

at readStream (/node_modules/body-parser/node_modules/raw-body/index.js:211:17) 
at getRawBody (/node_modules/body-parser/node_modules/raw-body/index.js:106:12)
at read (/node_modules/body-parser/lib/read.js:76:3)
at jsonParser (/node_modules/body-parser/lib/types/json.js:127:5)
这是我的代码:
var express = require('express');
var bodyParser = require('body-parser')

function myMiddleware() {
  return function(req, res, next) {
    req.rawBody = '';
    req.setEncoding('utf8');

    req.on('data', function(chunk) {
      req.rawBody += chunk;
    });

    req.on('end', function() {
      next();
    });
  }
}

var app = express();
app.use(myMiddleware());
app.use(bodyParser.json());

var listener = app.listen(3000, function() {
});

app.get('/webhook/', function (req, res) {
  res.sendStatus(200);
});

有没有一种方法可以取消编码?还有另一种方法可以检索原始主体,但仍然在使用body-parser之后?


1
在bodyParser之后使用你的中间件? - nicovank
你的代码res.sendStatu(200);也有打字错误。 - doublesharp
@doublesharp 你说得对,我可能不需要调用 setEncoding(),我之前以为必须这样做,因为没有它,应用程序会挂起。现在我明白了,任何试图第二次读取正文的人都会挂起,这种情况下就是 body-parser - kiewic
正确,因为在数据发出“end”之后才会调用 next()。试着只设置事件处理程序,然后在最后调用 next()不要在处理程序中调用。 - doublesharp
糟糕,您的自定义中间件有误。myMiddleware 中的函数从未被调用,因此 next 也没有被调用。 - doublesharp
显示剩余3条评论
3个回答

11

原来 body-parser 有一个 verify 选项,可以在请求体被读取后调用一个函数。该函数将以缓冲区形式接收请求体。

以下是一个示例:

var express = require('express');
var bodyParser = require('body-parser')

function verifyRequest(req, res, buf, encoding) {
  // The raw body is contained in 'buf'
  console.log( buf.toString( encoding ) );
};

var app = express();
var listener = app.listen(3000);

// Hook 'verifyRequest' with body-parser here.
app.use(bodyParser.json({ verify: verifyRequest }))

app.post('/webhook/', function (req, res) {
  res.status(200).send("done!");
});

6
您正在“done”函数内部调用next(),这意味着流已经被消耗。相反,请设置“data”的处理程序,然后使用next()传递请求。 “done”事件可能在bodyParser内进行处理,在执行后,您可以访问req.rawBody。如果不是这种情况,则需要添加另一个中间件,在req.on('done')内部调用next(),以防止在获取所有数据之前处理其余部分。
// custom middleware - req, res, next must be arguments on the top level function
function myMiddleware(req, res, next) {
  req.rawBody = '';

  req.on('data', function(chunk) {
    req.rawBody += chunk;
  });

  // call next() outside of 'end' after setting 'data' handler
  next();  
}

// your middleware
app.use(myMiddleware);

// bodyparser
app.use(bodyParser.json())

// test that it worked
function afterMiddleware(req, res, next) {
  console.log(req.rawBody);
  next();  
}

app.use(afterMiddleware);

如果你需要访问原始请求体(raw body),也可以考虑使用bodyParser.raw()。这将把原始请求体放入req.body中,与bodyParse.json()相同,但可以根据内容类型有条件地运行 - 查看options.type

1
in your example the inner function is never being called yes it is.... the function myMiddleware is returning the function and he calls it later app.use(myMiddleware()) - nicovank
你是正确的,但它没有传递 req, res, next,所以它们没有在正确的上下文中执行。 - doublesharp
编写中间件处理程序作为内部函数,允许调用者在设置时间传递参数选项给中间件。 - kiewic
我对这个解决方案有一个新的问题,app.get('/', function (req, res) { }) 不再被调用了。 - kiewic

1

我建议采用不同的方法,因为您当前的方法实际上会消耗消息,并使其无法被body-parser读取(通过同步调用next会出现一堆边缘情况错误):

app.use(bodyParser.json());
app.use(bodyParser.text({type: '*/*'}));

这将读取任何application/json请求作为JSON,其他所有内容都作为文本。
如果您必须除了文本之外也得到JSON对象,我建议自己解析它:
app.use(bodyParser.text({type: '*/*'}));
app.use(myMiddleware);

function myMiddleware(req, res, next) {
    req.rawBody = req.body;
    if(req.headers['content-type'] === 'application/json') {
        req.body = JSON.parse(req.body);
    }
    next();
}

我认为自己解析它是最好的方法。 - kiewic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接