Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/multiple files in body #66

Merged
merged 9 commits into from
May 22, 2019
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ jspm_packages

# Ignore package-lock
package-lock.json
.vscode
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,13 @@ fastify.post('/', function (req, reply) {
// {
// myStringField: 'example',
// anotherOne: 'example',
// myFilenameField: {
// myFilenameField: [{
// data: <Buffer>,
// encoding: '7bit',
// filename: 'README.md',
// limit: false,
// mimetype: 'text/markdown'
// }
// }]
// }

reply.code(200).send()
Expand All @@ -146,8 +146,10 @@ fastify.post('/', function (req, reply) {
The options `onFile` and `sharedSchemaId` will be used only when `addToBody: true`.

The `onFile` option define how the file streams are managed:
+ if you don't set it the `req.body.<fieldName>.data` will be a Buffer with the data loaded in memory
+ if you set it with a function you **must** consume the stream and the an the `req.body.<fieldName>.data` will be an empty array
+ if you don't set it the `req.body.<fieldName>[index].data` will be a Buffer with the data loaded in memory
+ if you set it with a function you **must** consume the stream, and the `req.body.<fieldName>[index].data` will be an empty array

**Note**: By default values in fields with files have array type, so if there's only one file uploaded, you can access it via `req.body.<fieldName>[0].data`

The `sharedSchemaId` parameter must provide a string ID and a [shared schema](https://github.com/fastify/fastify/blob/master/docs/Validation-and-Serialization.md#adding-a-shared-schema) will be added to your fastify instance so you will be able to apply the validation to your service like this:

Expand All @@ -159,7 +161,7 @@ fastify.post('/upload', {
required: ['myStringField', 'myFilenameField'],
properties: {
myStringField: { type: 'string' },
myFilenameField: 'MultipartFileType#'
myFilenameField: { type: 'array', items: 'MultipartFileType#' }
}
}
}, function (req, reply) {
Expand Down
10 changes: 6 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ function attachToBody (options, req, reply, next) {
const consumerStream = options.onFile || defaultConsumer
const body = { }
const mp = req.multipart((field, file, filename, encoding, mimetype) => {
body[field] = {
body[field] = body[field] || []
body[field].push({
data: [],
filename,
encoding,
mimetype,
limit: false
}
})

const result = consumerStream(field, file, filename, encoding, mimetype, body)
if (result && typeof result.then === 'function') {
Expand All @@ -51,10 +52,11 @@ function attachToBody (options, req, reply, next) {

function defaultConsumer (field, file, filename, encoding, mimetype, body) {
const fileData = []
const lastFile = body[field][body[field].length - 1]
file.on('data', data => { fileData.push(data) })
file.on('limit', () => { body[field].limit = true })
file.on('limit', () => { lastFile.limit = true })
file.on('end', () => {
body[field].data = Buffer.concat(fileData)
lastFile.data = Buffer.concat(fileData)
})
}

Expand Down
121 changes: 94 additions & 27 deletions test/append-body.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const pump = require('pump')

const filePath = path.join(__dirname, '../README.md')

test('append to body option', t => {
test('addToBody option', t => {
t.plan(8)

const fastify = Fastify()
Expand All @@ -21,14 +21,14 @@ test('append to body option', t => {
fastify.post('/', function (req, reply) {
t.equal(req.body.myField, 'hello')
t.equal(req.body.myCheck, 'true')
t.like(req.body.myFile, {
t.like(req.body.myFile, [{
encoding: '7bit',
filename: 'README.md',
limit: false,
mimetype: 'text/markdown'
})
t.type(req.body.myFile.data, Buffer)
t.equal(req.body.myFile.data.toString('utf8').substr(0, 19), '# fastify-multipart')
}])
t.type(req.body.myFile[0].data, Buffer)
t.equal(req.body.myFile[0].data.toString('utf8').substr(0, 19), '# fastify-multipart')

reply.send('ok')
})
Expand Down Expand Up @@ -63,7 +63,7 @@ test('append to body option', t => {
})
})

test('append to body option and multiple files', t => {
test('addToBody option and multiple files', t => {
t.plan(7)

const fastify = Fastify()
Expand All @@ -80,29 +80,29 @@ test('append to body option and multiple files', t => {
fastify.register(multipart, opts)

fastify.post('/', function (req, reply) {
t.like(req.body.myFile, {
t.like(req.body.myFile, [{
data: [],
encoding: '7bit',
filename: 'README.md',
limit: false,
mimetype: 'text/markdown'
})
}])

t.like(req.body.myFileTwo, {
t.like(req.body.myFileTwo, [{
data: [],
encoding: '7bit',
filename: 'README.md',
limit: false,
mimetype: 'text/markdown'
})
}])

t.like(req.body.myFileThree, {
t.like(req.body.myFileThree, [{
data: [],
encoding: '7bit',
filename: 'README.md',
limit: false,
mimetype: 'text/markdown'
})
}])

t.equal(fileCounter, 3, 'We must receive 3 file events')
reply.send('ok')
Expand Down Expand Up @@ -140,7 +140,74 @@ test('append to body option and multiple files', t => {
})
})

test('append to body option and custom stream management', t => {
test('addToBody option and multiple files in one field', t => {
t.plan(4)

const fastify = Fastify()
t.tearDown(fastify.close.bind(fastify))

const opts = {
addToBody: true
}
fastify.register(multipart, opts)

fastify.post('/', function (req, reply) {
t.like(req.body.myFile, [{
data: [],
encoding: '7bit',
filename: 'README.md',
limit: false,
mimetype: 'text/markdown'
}, {
data: [],
encoding: '7bit',
filename: 'LICENSE',
limit: false,
mimetype: 'application/octet-stream'
}, {
data: [],
encoding: '7bit',
filename: 'form.html',
limit: false,
mimetype: 'text/html'
}])

reply.send('ok')
})

fastify.listen(0, function () {
// request
var form = new FormData()
var opts = {
protocol: 'http:',
hostname: 'localhost',
port: fastify.server.address().port,
path: '/',
headers: form.getHeaders(),
method: 'POST'
}

var req = http.request(opts, (res) => {
t.equal(res.statusCode, 200)
res.resume()
res.on('end', () => {
t.pass('res ended successfully')
})
})

var rs1 = fs.createReadStream(path.join(__dirname, '../README.md'))
var rs2 = fs.createReadStream(path.join(__dirname, '../LICENSE'))
var rs3 = fs.createReadStream(path.join(__dirname, '../form.html'))
form.append('myFile', rs1)
form.append('myFile', rs2)
form.append('myFile', rs3)
pump(form, req, function (err) {
t.error(err, 'client pump: no err')
})
})
})

test('addToBody option and custom stream management', t => {
t.plan(7)

const fastify = Fastify()
Expand All @@ -158,13 +225,13 @@ test('append to body option and custom stream management', t => {
fastify.post('/', function (req, reply) {
t.equal(req.body.myField, 'hello')
t.equal(req.body.myCheck, 'true')
t.like(req.body.myFile, {
t.like(req.body.myFile, [{
data: [],
encoding: '7bit',
filename: 'README.md',
limit: false,
mimetype: 'text/markdown'
})
}])

reply.send('ok')
})
Expand Down Expand Up @@ -199,7 +266,7 @@ test('append to body option and custom stream management', t => {
})
})

test('append to body option with promise', t => {
test('addToBody option with promise', t => {
t.plan(5)

const fastify = Fastify()
Expand All @@ -216,13 +283,13 @@ test('append to body option with promise', t => {
fastify.register(multipart, opts)

fastify.post('/', function (req, reply) {
t.like(req.body.myFile, {
t.like(req.body.myFile, [{
data: [],
encoding: '7bit',
filename: 'README.md',
limit: false,
mimetype: 'text/markdown'
})
}])

reply.send('ok')
})
Expand Down Expand Up @@ -255,7 +322,7 @@ test('append to body option with promise', t => {
})
})

test('append to body option with promise in error', t => {
test('addToBody option with promise in error', t => {
t.plan(3)

const fastify = Fastify()
Expand Down Expand Up @@ -301,7 +368,7 @@ test('append to body option with promise in error', t => {
})
})

test('append to body with shared schema', t => {
test('addToBody with shared schema', t => {
t.plan(9)

const fastify = Fastify()
Expand All @@ -327,19 +394,19 @@ test('append to body with shared schema', t => {
required: ['myField', 'myFile'],
properties: {
myField: { type: 'string' },
myFile: 'mySharedSchema#'
myFile: { type: 'array', items: 'mySharedSchema#' }
}
}
}
}, function (req, reply) {
t.equal(req.body.myField, 'hello')
t.like(req.body.myFile, {
t.like(req.body.myFile, [{
data: [],
encoding: '7bit',
filename: 'README.md',
limit: false,
mimetype: 'text/markdown'
})
}])

reply.send('ok')
})
Expand Down Expand Up @@ -373,7 +440,7 @@ test('append to body with shared schema', t => {
})
})

test('append to body with shared schema error', t => {
test('addToBody with shared schema error', t => {
t.plan(3)

const fastify = Fastify()
Expand All @@ -392,7 +459,7 @@ test('append to body with shared schema error', t => {
required: ['myField', 'myFile'],
properties: {
myField: { type: 'string' },
myFile: 'mySharedSchema#'
myFile: { type: 'array', items: 'mySharedSchema#' }
}
}
}
Expand Down Expand Up @@ -429,7 +496,7 @@ test('append to body with shared schema error', t => {
})
})

test('append to body without files and shared schema', t => {
test('addToBody without files and shared schema', t => {
t.plan(5)

const fastify = Fastify()
Expand Down Expand Up @@ -490,7 +557,7 @@ test('append to body without files and shared schema', t => {
})
})

test('append to body option does not change behaviour on not-multipart request', t => {
test('addToBody option does not change behaviour on not-multipart request', t => {
t.plan(2)

const fastify = Fastify()
Expand Down