-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Provide
file
mode to handle multipart request (#19)
see RFC to get detail: eggjs/egg#3052 closes eggjs/egg#3052
- Loading branch information
Showing
22 changed files
with
873 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
'use strict'; | ||
|
||
const path = require('path'); | ||
const fs = require('mz/fs'); | ||
const uuid = require('uuid'); | ||
const mkdirp = require('mz-modules/mkdirp'); | ||
const pump = require('mz-modules/pump'); | ||
const sendToWormhole = require('stream-wormhole'); | ||
const moment = require('moment'); | ||
|
||
module.exports = options => { | ||
async function cleanup(requestFiles) { | ||
for (const file of requestFiles) { | ||
try { | ||
await fs.unlink(file.filepath); | ||
} catch (_) { | ||
// do nothing | ||
} | ||
} | ||
} | ||
|
||
async function limit(requestFiles, code, message) { | ||
// cleanup requestFiles | ||
await cleanup(requestFiles); | ||
|
||
// throw 413 error | ||
const err = new Error(message); | ||
err.code = code; | ||
err.status = 413; | ||
throw err; | ||
} | ||
|
||
return async function multipart(ctx, next) { | ||
if (!ctx.is('multipart')) return next(); | ||
|
||
let storedir; | ||
|
||
const requestBody = {}; | ||
const requestFiles = []; | ||
|
||
const parts = ctx.multipart({ autoFields: false }); | ||
let part; | ||
do { | ||
try { | ||
part = await parts(); | ||
} catch (err) { | ||
await cleanup(requestFiles); | ||
throw err; | ||
} | ||
|
||
if (!part) break; | ||
|
||
if (part.length) { | ||
ctx.coreLogger.debug('[egg-multipart:storeMultipart] handle value part: %j', part); | ||
const fieldnameTruncated = part[2]; | ||
const valueTruncated = part[3]; | ||
if (valueTruncated) { | ||
return await limit(requestFiles, 'Request_fieldSize_limit', 'Reach fieldSize limit'); | ||
} | ||
if (fieldnameTruncated) { | ||
return await limit(requestFiles, 'Request_fieldNameSize_limit', 'Reach fieldNameSize limit'); | ||
} | ||
|
||
// arrays are busboy fields | ||
requestBody[part[0]] = part[1]; | ||
continue; | ||
} | ||
|
||
// otherwise, it's a stream | ||
const meta = { | ||
field: part.fieldname, | ||
filename: part.filename, | ||
encoding: part.encoding, | ||
mime: part.mime, | ||
}; | ||
ctx.coreLogger.debug('[egg-multipart:storeMultipart] handle stream part: %j', meta); | ||
// empty part, ignore it | ||
if (!part.filename) { | ||
await sendToWormhole(part); | ||
continue; | ||
} | ||
|
||
if (!storedir) { | ||
// ${tmpdir}/YYYY/MM/DD/HH | ||
storedir = path.join(options.tmpdir, moment().format('YYYY/MM/DD/HH')); | ||
const exists = await fs.exists(storedir); | ||
if (!exists) { | ||
await mkdirp(storedir); | ||
} | ||
} | ||
const filepath = path.join(storedir, uuid.v4() + path.extname(meta.filename)); | ||
const target = fs.createWriteStream(filepath); | ||
await pump(part, target); | ||
// https://github.com/mscdex/busboy/blob/master/lib/types/multipart.js#L221 | ||
meta.filepath = filepath; | ||
requestFiles.push(meta); | ||
|
||
// https://github.com/mscdex/busboy/blob/master/lib/types/multipart.js#L221 | ||
if (part.truncated) { | ||
return await limit(requestFiles, 'Request_fileSize_limit', 'Reach fileSize limit'); | ||
} | ||
} while (part != null); | ||
|
||
ctx.request.body = requestBody; | ||
ctx.request.files = requestFiles; | ||
return next(); | ||
}; | ||
}; |
Oops, something went wrong.