moleculerjs / moleculer-web

:earth_africa: Official API Gateway service for Moleculer framework
http://moleculer.services/docs/moleculer-web.html
MIT License
292 stars 118 forks source link

multipartHandler throws unhandled error if payload is too large #321

Open disce-omnes opened 1 year ago

disce-omnes commented 1 year ago

Prerequisites

Please answer the following questions for yourself before submitting an issue.

Current Behavior

If a fileSize limit is specified in a multpart upload route, then upon reaching the limit an unhandled PayloadTooLarge error is thrown & the process crashes. API gateway error handlers don't catch this error.

busboy allows you to define onFileSizeLimit handler, however I haven't found a way to prevent the error from being thrown (it's thrown on the next line after the handler call).

Expected Behavior

Route or global error handler should handle the error. If no error handlers are defined, the default error handler should handle the error.

Or onFileSizeLimit handler should provide a way (specified in documentation) to cancel throwing unhandled error.

The process shouldn't crash.

I can submit a PR with the fix, especially if given advice on how to refactor it best.

Failure Information

Error is thrown here

Reproduce code snippet

const { ServiceBroker, Service } = require('moleculer')
const ApiGateway = require('moleculer-web')
const fs = require('fs/promises')

class FileUploadService extends Service {
  constructor(broker) {
    super(broker)

    this.parseServiceSchema({
      name: 'fileUpload',
      mixins: [ApiGateway],

      settings: {
        port: 3001,

        onError(req, res, err) {
          console.log('global error handler')
          res.setHeader('Content-Type', 'text/plain')
          res.writeHead(501)
          res.end('Global error: ' + err.message)
        },

        routes: [{
          path: '/upload',
          mappingPolicy: 'restrict',

          onError(req, res, err) {
            console.log('route error handler')
            res.setHeader('Content-Type', 'application/json; charset=utf-8')
            res.writeHead(500)
            res.end(JSON.stringify(err))
          },

          aliases: {
            'POST /multi': {
              action: 'fileUpload.handleMulti',
              type: 'multipart',
              busboyConfig: {
                limits: { fileSize: 1_024 }, // 1KB
              },
            },
          },
        }],
      },
      actions: {
        async handleMulti(ctx) {
          console.log('handling multi')
          await fs.writeFile('./out', ctx.params)
          return 'success'
        },
      },
    })
  }
}

const broker = new ServiceBroker()
broker.createService(FileUploadService)
broker.start()
curl --request POST \
  --url http://127.0.0.1:3001/upload/multi \
  --header 'Content-Type: multipart/form-data' \
  --form file=@./test.file

Context

Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions.

Failure Logs

node:events:491
      throw er; // Unhandled 'error' event
      ^

PayloadTooLarge: Payload too large
    at FileStream.<anonymous> (node_modules/moleculer-web/src/alias.js:161:18)
    at FileStream.emit (node:events:513:28)
    at PartStream.onData (node_modules/@fastify/busboy/lib/types/multipart.js:205:18)
    at PartStream.emit (node:events:513:28)
    at addChunk (node:internal/streams/readable:315:12)
    at readableAddChunk (node:internal/streams/readable:289:9)
    at PartStream.Readable.push (node:internal/streams/readable:228:10)
    at Dicer._oninfo (node_modules/@fastify/busboy/deps/dicer/lib/Dicer.js:162:36)
    at Dicer._oninfo (node_modules/@fastify/busboy/deps/dicer/lib/Dicer.js:167:65)
    at SBMH.<anonymous> (node_modules/@fastify/busboy/deps/dicer/lib/Dicer.js:108:10)
Emitted 'error' event on FileStream instance at:
    at emitErrorNT (node:internal/streams/destroy:157:8)
    at emitErrorCloseNT (node:internal/streams/destroy:122:3)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {
  code: 413,
  type: 'PAYLOAD_TOO_LARGE',
  data: {
    fieldname: 'file',
    filename: 'test.csv',
    encoding: '7bit',
    mimetype: 'text/csv'
  },
  retryable: false
}