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 extensions

For security, if uploading file extension 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: '50mb',
};

# Custom Config

Developer can custom additional file extensions:

// 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

More 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');
const Controller = require('egg').Controller;

module.exports = Class UploadController extends Controller {
async upload() {
const ctx = this.ctx;
const stream = await ctx.getFileStream();
const name = 'egg-multipart-test/' + path.basename(stream.filename);
let result;
try {
// process file or upload to cloud storage
result = await ctx.oss.put(name, stream);
} catch (err) {
// must consume the stream, otherwise browser will be stuck.
await 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');
const Controller = require('egg').Controller;

module.exports = Class UploadController extends Controller {
async upload() {
const ctx = this.ctx;
const parts = ctx.multipart();
let part;
while ((part = await 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 = await ctx.oss.put('egg-multipart-test/' + part.filename, part);
} catch (err) {
await sendToWormhole(part);
throw err;
}
console.log(result);
}
}
console.log('and we are done parsing the form!');
}
};