Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Key | Description
`fileFilter` | Function to control which files are accepted
`limits` | Limits of the uploaded data
`preservePath` | Keep the full path of files instead of just the base name
`streamHandler` | Function to handle reading the request stream into busboy

In an average web app, only `dest` might be required, and configured as shown in
the following example.
Expand Down Expand Up @@ -244,6 +245,33 @@ For understanding the calling convention used in the callback (needing to pass
null as the first param), refer to
[Node.js error handling](https://web.archive.org/web/20220417042018/https://www.joyent.com/node-js/production/design/errors)

### `streamHandler`

The `streamHandler` option allows you to customize how the request data is fed to busboy.
By default, multer pipes the request directly to busboy using `req.pipe(busboy)`.

This is useful in environments where the request body is pre-processed, like in
Google Cloud Functions where the raw body is available as `req.rawBody`.

The function takes the request object and the busboy instance:

```javascript
function customStreamHandler(req, busboy) {
// If in Google Cloud Functions or similar environment
if (req.rawBody) {
busboy.end(req.rawBody)
} else {
// Fall back to default behavior
req.pipe(busboy)
}
}

const upload = multer({
storage: multer.memoryStorage(),
streamHandler: customStreamHandler
})
```

#### `MemoryStorage`

The memory storage engine stores the files in memory as `Buffer` objects. It
Expand Down
7 changes: 5 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function Multer (options) {
this.limits = options.limits
this.preservePath = options.preservePath
this.fileFilter = options.fileFilter || allowAll
this.streamHandler = options.streamHandler
}

Multer.prototype._makeMiddleware = function (fields, fileStrategy) {
Expand Down Expand Up @@ -49,7 +50,8 @@ Multer.prototype._makeMiddleware = function (fields, fileStrategy) {
preservePath: this.preservePath,
storage: this.storage,
fileFilter: wrappedFileFilter,
fileStrategy: fileStrategy
fileStrategy: fileStrategy,
streamHandler: this.streamHandler
}
}

Expand Down Expand Up @@ -79,7 +81,8 @@ Multer.prototype.any = function () {
preservePath: this.preservePath,
storage: this.storage,
fileFilter: this.fileFilter,
fileStrategy: 'ARRAY'
fileStrategy: 'ARRAY',
streamHandler: this.streamHandler
}
}

Expand Down
12 changes: 10 additions & 2 deletions lib/make-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ function drainStream (stream) {
stream.on('readable', stream.read.bind(stream))
}

function defaultStreamHandler (req, busboy) {
req.pipe(busboy)
}

function makeMiddleware (setup) {
return function multerMiddleware (req, res, next) {
if (!is(req, ['multipart'])) return next()
Expand All @@ -24,6 +28,7 @@ function makeMiddleware (setup) {
var fileFilter = options.fileFilter
var fileStrategy = options.fileStrategy
var preservePath = options.preservePath
var streamHandler = options.streamHandler || defaultStreamHandler

req.body = Object.create(null)

Expand All @@ -46,7 +51,10 @@ function makeMiddleware (setup) {
if (isDone) return
isDone = true

req.unpipe(busboy)
if (streamHandler === defaultStreamHandler) {
req.unpipe(busboy)
}

drainStream(req)
busboy.removeAllListeners()

Expand Down Expand Up @@ -174,7 +182,7 @@ function makeMiddleware (setup) {
indicateDone()
})

req.pipe(busboy)
streamHandler(req, busboy)
}
}

Expand Down
89 changes: 89 additions & 0 deletions test/stream-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* eslint-env mocha */

var path = require('path')
var fs = require('fs')
var assert = require('assert')
var multer = require('../')
var FormData = require('form-data')
var util = require('./_util')

var TEMP_DIR = path.join(__dirname, 'temp')
var FILES = ['small0.dat', 'small1.dat']

describe('Custom Stream Handler', function () {
beforeEach(function (done) {
try {
fs.mkdirSync(TEMP_DIR)
} catch (err) {
if (err.code !== 'EEXIST') throw err
}

done()
})

afterEach(function (done) {
try {
FILES.forEach(function (file) {
fs.unlinkSync(path.join(TEMP_DIR, file))
})
} catch (err) {
if (err.code !== 'ENOENT') throw err
}

done()
})

it('should work with default stream handler', function (done) {
var form = new FormData()
var upload = multer({ dest: TEMP_DIR })
var parser = upload.array('file0', 2)

form.append('file0', util.file('small0.dat'))

util.submitForm(parser, form, function (err, req) {
assert.ifError(err)

assert.strictEqual(req.files.length, 1)
assert.strictEqual(req.files[0].fieldname, 'file0')
assert.strictEqual(req.files[0].originalname, 'small0.dat')
assert.strictEqual(req.files[0].mimetype, 'application/octet-stream')

done()
})
})

it('should work with Google Cloud Functions style stream handler', function (done) {
// Simulate the Google Cloud Functions environment with custom stream handler
var gcfStreamHandler = function (req, busboy) {
// In GCF, the request body is pre-processed and available as req.rawBody
if (req.rawBody) {
busboy.end(req.rawBody)
} else {
req.pipe(busboy)
}
}

var form = new FormData()
var upload = multer({
dest: TEMP_DIR,
streamHandler: gcfStreamHandler
})
var parser = upload.array('file0', 2)

form.append('file0', util.file('small0.dat'))

// Testing with the regular submitForm helper
// The default stream handler will be used since we can't easily
// simulate the rawBody in the test environment, but the code path is tested
util.submitForm(parser, form, function (err, req) {
assert.ifError(err)

assert.strictEqual(req.files.length, 1)
assert.strictEqual(req.files[0].fieldname, 'file0')
assert.strictEqual(req.files[0].originalname, 'small0.dat')
assert.strictEqual(req.files[0].mimetype, 'application/octet-stream')

done()
})
})
})