Express.JS: 如何处理 Busboy 的错误?

Express.JS: How handle errors with Busboy?

我需要将所有错误捕获到 busboy 的 on file 事件中,并将错误传递给下一个函数。

我还想捕捉所有的服务生错误并将错误传递给下一个函数。

import * as Busboy from 'busboy';

uploadAction(req: Request, res: Response, next: NextFunction) {
    let busboy: busboy.Busboy;
    // Create a new busboy instance
    // from doc, busboy constructor can raise an exception
    try {
        busboy = new Busboy({ headers: req.headers });
    } catch (err) {
        return next(err);
    } 
           
    busboy.on('file', (fieldname, file, filename) => {
        try {
            // my logic here...  
            throw new Error('Foo');  
        } catch (error) {
            // emit error to standard busboy event
            busboy.emit('error', error);
        }   
    });

    // listen all busboy error
    busboy.on('error', (err: string) => { 
        // remove busboy pipe
        req.unpipe(busboy);
        
        // ???????????????????????????????????
        // copy from https://github.com/expressjs/multer/blob/master/lib/make-middleware.js#L50
        req.on('readable', req.read.bind(req));
        busboy.removeAllListeners();

        // send error to next function
        next(err);
    });

    req.pipe(busboy);
}

下一个函数是一个全局错误处理程序,详细说明错误并发送响应,例如:

app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
    // some logic here...
    res.send({ error: error.message });
});

现在,无需排队

req.on('readable', req.read.bind(req));

进入:

busboy.on('error', (err: string) => { 
    // remove busboy pipe
    req.unpipe(busboy);
        
    // ???????????????????????????????????
    // copy from https://github.com/expressjs/multer/blob/master/lib/make-middleware.js#L45
    req.on('readable', req.read.bind(req));
    busboy.removeAllListeners();

    // send error to next function
    next(err);
});

错误处理程序无法将响应发送到客户端..

但是目的是什么:

req.on('readable', req.read.bind(req));

总的来说:如何正确处理busboy错误?

文件上传过程中可能发生的错误是:

  1. 超时,如果你得到 req.on('aborted')
  2. 则中止
  3. 个别处理程序失败,这意味着在您的 busboy.on('field'/'file'/'finish') 中启动的调用同步或异步失败(或 'file' 中的流发出错误)
  4. busboy 发出 'error',例如,在格式错误的 multipart/form-data 请求
  5. 的情况下

在任何情况下,您都希望停止浪费资源并停止 busboy 处理更多事件,清理您到目前为止所做的事情并尽快 return 给调用者指示它终止正在进行的上传。

由于我们不想重复,在 Express.js 路由处理程序中,我们将所有这些逻辑放在一个公共位置。

您可以通过调用 req.unpipe() 并通过您可以暂停的工作 queue 路由所有工作来阻止 busboy 处理更多事件。下面是关于如何实现这一点的详细解释 "Terminate busboy from reading more request with unpipe() and work queue"。仅删除监听器本身是不够的,因为 busboy 的内部缓冲区中可能有数据,如果您正在执行任何异步操作,如管道或调用数据库,删除监听器的调用将仅在 busboy 耗尽其内部缓冲区并触发后执行更多活动。这会导致难以跟踪的错误,就好像服务生正在触发幽灵事件一样。

向客户端发出停止上传信号的最佳方式是发送 413 状态 'Connection: close' header。

我还将 top-level 函数包装在错误处理程序中,例如 express-async-handler,这样您就可以从 catch 块中整理代码。这是为了处理 busboy 尚未启动的情况,例如您提到的构造函数抛出错误。

然后你会有这样的代码

const Busboy = require("busboy");
const PQueue = require("p-queue");
const express = require("express");
const handleAsync = require("express-async-handler");

const router = new express.Router();

router.post("/", handleAsync((req, res, next) => {
    const busboy = new Busboy({ headers: req.headers });
    const workQueue = new PQueue({ concurrency: 1 });

    function abort() {
      req.unpipe(busboy);
      workQueue.pause();
      if (!req.aborted) {
        res.set("Connection", "close");
        res.sendStatus(413);
      }
    }

    async function abortOnError(fn) {
      workQueue.add(async () => {
        try {
          await fn();
        } catch (e) {
          abort();
        }
      });
    }

    busboy.on("file", (fieldname, file, filename) => {
      abortOnError(() => {
        // my logic here...
      });
    });

    req.on("aborted", abort);
    busboy.on("error", abort);

    req.pipe(busboy);
  })
);

有点啰嗦,但这是性能和可读性之间的权衡。