egg-multipart

NPM version build status Test coverage David deps Known Vulnerabilities npm download

Use co-busboy to upload file by streaming and process it without save to disk.

Just use ctx.multipart() to got file stream, then pass to image processing liberary such as gm or upload to cloud storage such as oss.

# Whitelist of file extendtions

For security, if uploading file extendtion is not in white list, will response as 400 Bad request.

Default Whitelist:

const whitelist = [
// images
'.jpg', '.jpeg', // image/jpeg
'.png', // image/png, image/x-png
'.gif', // image/gif
'.bmp', // image/bmp
'.wbmp', // image/vnd.wap.wbmp
'.webp',
'.tif',
'.psd',
// text
'.svg',
'.js', '.jsx',
'.json',
'.css', '.less',
'.html', '.htm',
'.xml',
// tar
'.zip',
'.gz', '.tgz', '.gzip',
// video
'.mp3',
'.mp4',
'.avi',
];

# fileSize

The default fileSize that multipart can accept is 10mb. if you upload a large file, you should specify this config.

// config/config.default.js
exports.multipart = {
fileSize: '50m',
};

# Custom Config

Developer can custom additional file extentions:

// config/config.default.js
exports.multipart = {
// will append to whilelist
fileExtensions: [
'.foo',
'.apk',
],
};

Can also override built-in whitelist, such as only allow png:

// config/config.default.js
exports.multipart = {
whitelist: [
'.png',
],
};

Or by function:

exports.multipart = {
whitelist: (filename) => [ '.png' ].includes(path.extname(filename) || '')
};

Note: if define whitelist, then fileExtensions will be ignored.

# Examples

# Upload File

You can got upload stream by ctx.getFileStream*().

<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
title: <input name="title" />
file: <input name="file" type="file" />
<button type="submit">Upload</button>
</form>

Controller which hanlder POST /upload:

// app/controller/upload.js
const path = require('path');
const sendToWormhole = require('stream-wormhole');
module.exports = function* (ctx) {
const stream = yield ctx.getFileStream();
const name = 'egg-multipart-test/' + path.basename(stream.filename);
let result;
try {
// process file or upload to cloud storage
result = yield ctx.oss.put(name, stream);
} catch (err) {
// must consume the stream, otherwise browser will be stuck.
yield sendToWormhole(stream);
throw err;
}
ctx.body = {
url: result.url,
// process form fields by `stream.fields`
fields: stream.fields,
};
};

# Upload Multiple Files

<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
title: <input name="title" />
file: <input name="file" type="file" />
<button type="submit">Upload</button>
</form>

Controller which hanlder POST /upload:

// app/controller/upload.js
const sendToWormhole = require('stream-wormhole');
module.exports = function* (ctx) {
const parts = ctx.multipart();
let part;
while ((part = yield parts) != null) {
if (part.length) {
// arrays are busboy fields
console.log('field: ' + part[0]);
console.log('value: ' + part[1]);
console.log('valueTruncated: ' + part[2]);
console.log('fieldnameTruncated: ' + part[3]);
} else {
if (!part.filename) {
// user click `upload` before choose a file,
// `part` will be file stream, but `part.filename` is empty
// must handler this, such as log error.
return;
}
// otherwise, it's a stream
console.log('field: ' + part.fieldname);
console.log('filename: ' + part.filename);
console.log('encoding: ' + part.encoding);
console.log('mime: ' + part.mime);
let result;
try {
result = yield ctx.oss.put('egg-multipart-test/' + part.filename, part);
} catch (err) {
yield sendToWormhole(stream);
throw err;
}
console.log(result);
}
}
console.log('and we are done parsing the form!');
}