如何在中间件中获得响应体?

时间:2019-08-19 06:43:49

标签: node.js express

我想记录每个请求的响应信息。

我尝试使用中间件,但是有问题。

res.body未定义。

app.use((req, res, next) => {

    var time = Date.now();

    res.on('finish', function() {
        var clientIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
        var method = req.method;
        var path = req.baseUrl;
        var duration = Date.now() - time;

        console.log({
            clientIp,
            elapsedTime: `${duration}ms`,
            hostname: req.headers.host,
            level: 'INFO',
            method,
            path,
            phase: process.env.NODE_ENV,
            reqBody: req.body,
            reqHeaders: req.headers,
            resBody: res.body,
            resHeaders: res.getHeaders(),
            status: res.statusCode
        });
    });

    next();
});

实际上,客户端上有响应数据。

如何在中间件中获取响应正文?

3 个答案:

答案 0 :(得分:2)

可以通过重写response.json函数来截获响应。这样,并添加我们的custom function,每次调用response.json()时,都会触发我们的拦截功能。

middleware / response.filter.js:

// Response Interceptor Middleware
export default (request, response, next) => {
    try {
        const oldJSON = response.json;
        response.json = (data) => {
            // For Async call, handle the promise and then set the data to `oldJson`
            if (data && data.then != undefined) {
                // Resetting json to original to avoid cyclic call.
                return data.then((responseData) => {
                    // Custom logic/code.
                    response.json = oldJSON;
                    return oldJSON.call(response, responseData);
                }).catch((error) => {
                    next(error);
                });
            } else {
                // For non-async interceptor functions
                // Resetting json to original to avoid cyclic call.
                // Custom logic/code.
                response.json = oldJSON;
                return oldJSON.call(response, finalResponse);
            }
        }
    } catch (error) {
        next(error);
    }
}

Server.js文件中,注册中间件:

// Server.js file
import externalResponseFilter from "./middleware/response.filter.js:";

// Create Express server
const app = express();

// Response interceptor - Initialization.
app.use(externalResponseFilter);

在要返回response的控制器中,使用response.json()函数而不是response.send()返回。

让我知道是否需要其他说明。

答案 1 :(得分:0)

Express中的响应对象只是节点的http.ServerResponse类。它是可写的Stream,因此您可以将其作为Stream进行交互。它还通过net.Socket公开了基础res.socket连接。

这是有趣的地方。因为net.Socket是普通可写流。如果您使用.write()方法覆盖它,则将能够拦截从套接字到网络的所有字节:

function interceptorMiddleware (req, res, next) {
    const sock  = req.socket;
    const write = sock.write.bind(sock);

    sock.write = (data, encoding, callback) => {
        console.log(data);
        write(data, encoding, callback);
    }

    sock.on('close', () => console.log('DONE'));

    next();
}

您需要在应用程序中尽早安装此中间件,然后才能有任何机会写入套接字。

由于节点的异步特性,您可能会在日志输出中得到混合的数据包,因此可能值得添加一些代码来标识哪个套接字正在生成输出:

console.log(sock.remoteAddress, sock.remotePort, data);
  

警告!

     

console.log()在输出到终端或文件时是同步的。   这样做可能会严重降低服务器的性能。   如果您将stdout用管道传输到另一个进程(例如pm2),那还不错   因为它将是异步的,但仍可能会给您带来轻微的性能损失。

     

将此用于调试

答案 2 :(得分:0)

截取来自 res.json 的响应正文并将其存储在 res.locals 中以供稍后访问。

app.express.use((req, res, next) => {
  const oldJson = res.json;
  res.json = (body) => {
    res.locals.body = body;
    return oldJson.call(res, body);
  };
  next();
});