From a1fcdab00ef1113845bbe41a4c0b40ce9356cc94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TZ=20=7C=20=E5=A4=A9=E7=8C=AA?= Date: Fri, 9 Aug 2019 15:43:39 +0800 Subject: [PATCH] feat: fileModeMatch support glob with egg-path-matching (#36) --- README.md | 2 + app.js | 2 - app/middleware/multipart.js | 7 +- config/config.default.js | 2 +- index.d.ts | 4 +- package.json | 1 + .../fileModeMatch-glob/app/controller/save.js | 9 ++ .../app/controller/upload.js | 8 ++ .../apps/fileModeMatch-glob/app/router.js | 7 ++ .../fileModeMatch-glob/app/views/home.html | 8 ++ .../config/config.default.js | 8 ++ .../config/config.unittest.js | 8 ++ .../apps/fileModeMatch-glob/package.json | 3 + test/stream-mode-with-filematch-glob.test.js | 95 +++++++++++++++++++ test/wrong-mode.test.js | 12 --- 15 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 test/fixtures/apps/fileModeMatch-glob/app/controller/save.js create mode 100644 test/fixtures/apps/fileModeMatch-glob/app/controller/upload.js create mode 100644 test/fixtures/apps/fileModeMatch-glob/app/router.js create mode 100644 test/fixtures/apps/fileModeMatch-glob/app/views/home.html create mode 100644 test/fixtures/apps/fileModeMatch-glob/config/config.default.js create mode 100644 test/fixtures/apps/fileModeMatch-glob/config/config.unittest.js create mode 100644 test/fixtures/apps/fileModeMatch-glob/package.json create mode 100644 test/stream-mode-with-filematch-glob.test.js diff --git a/README.md b/README.md index 0443cd2..de80f87 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,8 @@ config.multipart = { mode: 'stream', // let POST /upload_file request use the file mode, other requests use the stream mode. fileModeMatch: /^\/upload_file$/, + // or glob + // fileModeMatch: '/upload_file', }; ``` diff --git a/app.js b/app.js index 578b82d..0e411f4 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,5 @@ 'use strict'; -const assert = require('assert'); const path = require('path'); const bytes = require('humanize-bytes'); @@ -94,7 +93,6 @@ module.exports = app => { // enable multipart middleware app.config.coreMiddleware.push('multipart'); } else if (options.fileModeMatch) { - assert(options.fileModeMatch instanceof RegExp, '`fileModeMatch` options should be an instance of RegExp'); app.coreLogger.info('[egg-multipart] will save temporary files to %j, cleanup job cron: %j', options.tmpdir, options.cleanSchedule.cron); // enable multipart middleware diff --git a/app/middleware/multipart.js b/app/middleware/multipart.js index b7d031f..34eb884 100644 --- a/app/middleware/multipart.js +++ b/app/middleware/multipart.js @@ -1,9 +1,14 @@ 'use strict'; +const pathMatching = require('egg-path-matching'); + module.exports = options => { + // normalize + const matchFn = options.fileModeMatch && pathMatching({ match: options.fileModeMatch }); + return async function multipart(ctx, next) { if (!ctx.is('multipart')) return next(); - if (options.fileModeMatch && !options.fileModeMatch.test(ctx.path)) return next(); + if (matchFn && !matchFn(ctx)) return next(); await ctx.saveRequestFiles(); return next(); diff --git a/config/config.default.js b/config/config.default.js index fc699a9..43687b4 100644 --- a/config/config.default.js +++ b/config/config.default.js @@ -12,7 +12,7 @@ module.exports = appInfo => { * @property {String} mode - which mode to handle multipart request, default is `stream`, the hard way. * If set mode to `file`, it's the easy way to handle multipart request and save it to local files. * If you don't know the Node.js Stream work, maybe you should use the `file` mode to get started. - * @property {RegExp} fileModeMatch - special url to use file mode when global `mode` is `stream`. + * @property {String | RegExp | Function | Array} fileModeMatch - special url to use file mode when global `mode` is `stream`. * @property {Boolean} autoFields - Auto set fields to parts, default is `false`. Only work on `stream` mode. * If set true,all fields will be auto handle and can acces by `parts.fields` * @property {String} defaultCharset - Default charset encoding, don't change it before you real know about it diff --git a/index.d.ts b/index.d.ts index c37028d..7bafdb1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -93,10 +93,12 @@ declare module 'egg' { files: EggFile[]; } + type MatchItem = string | RegExp | ((ctx: Context) => boolean); + interface EggAppConfig { multipart: { mode?: string; - fileModeMatch?: RegExp; + fileModeMatch?: MatchItem | MatchItem[]; autoFields?: boolean; defaultCharset?: string; fieldNameSize?: number; diff --git a/package.json b/package.json index 3a91124..34960ee 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ }, "dependencies": { "co-busboy": "^1.4.0", + "egg-path-matching": "^1.0.1", "humanize-bytes": "^1.0.1", "moment": "^2.22.2", "mz": "^2.7.0", diff --git a/test/fixtures/apps/fileModeMatch-glob/app/controller/save.js b/test/fixtures/apps/fileModeMatch-glob/app/controller/save.js new file mode 100644 index 0000000..def1972 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob/app/controller/save.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = async ctx => { + await ctx.saveRequestFiles(); + ctx.body = { + body: ctx.request.body, + files: ctx.request.files, + }; +}; diff --git a/test/fixtures/apps/fileModeMatch-glob/app/controller/upload.js b/test/fixtures/apps/fileModeMatch-glob/app/controller/upload.js new file mode 100644 index 0000000..2d86f94 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob/app/controller/upload.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = async ctx => { + ctx.body = { + body: ctx.request.body, + files: ctx.request.files, + }; +}; diff --git a/test/fixtures/apps/fileModeMatch-glob/app/router.js b/test/fixtures/apps/fileModeMatch-glob/app/router.js new file mode 100644 index 0000000..96b44aa --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob/app/router.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = app => { + app.post('/upload', app.controller.upload); + app.post('/upload_file', app.controller.upload); + app.post('/save', app.controller.save); +}; diff --git a/test/fixtures/apps/fileModeMatch-glob/app/views/home.html b/test/fixtures/apps/fileModeMatch-glob/app/views/home.html new file mode 100644 index 0000000..ce0ffb8 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob/app/views/home.html @@ -0,0 +1,8 @@ +
+ title: + file1: + file2: + file3: + other: + +
diff --git a/test/fixtures/apps/fileModeMatch-glob/config/config.default.js b/test/fixtures/apps/fileModeMatch-glob/config/config.default.js new file mode 100644 index 0000000..ecd0988 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob/config/config.default.js @@ -0,0 +1,8 @@ +'use strict'; + +exports.multipart = { + mode: 'stream', + fileModeMatch: '/upload_file' +}; + +exports.keys = 'multipart'; diff --git a/test/fixtures/apps/fileModeMatch-glob/config/config.unittest.js b/test/fixtures/apps/fileModeMatch-glob/config/config.unittest.js new file mode 100644 index 0000000..868ad8d --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob/config/config.unittest.js @@ -0,0 +1,8 @@ +'use strict'; + +exports.logger = { + consoleLevel: 'NONE', + coreLogger: { + // consoleLevel: 'DEBUG', + }, +}; diff --git a/test/fixtures/apps/fileModeMatch-glob/package.json b/test/fixtures/apps/fileModeMatch-glob/package.json new file mode 100644 index 0000000..77eb670 --- /dev/null +++ b/test/fixtures/apps/fileModeMatch-glob/package.json @@ -0,0 +1,3 @@ +{ + "name": "multipart-file-mode-demo" +} diff --git a/test/stream-mode-with-filematch-glob.test.js b/test/stream-mode-with-filematch-glob.test.js new file mode 100644 index 0000000..c838715 --- /dev/null +++ b/test/stream-mode-with-filematch-glob.test.js @@ -0,0 +1,95 @@ +'use strict'; + +const assert = require('assert'); +const formstream = require('formstream'); +const urllib = require('urllib'); +const path = require('path'); +const mock = require('egg-mock'); +const rimraf = require('mz-modules/rimraf'); + +describe('test/stream-mode-with-filematch-glob.test.js', () => { + let app; + let server; + let host; + before(() => { + app = mock.app({ + baseDir: 'apps/fileModeMatch-glob', + }); + return app.ready(); + }); + before(() => { + server = app.listen(); + host = 'http://127.0.0.1:' + server.address().port; + }); + after(() => { + return rimraf(app.config.multipart.tmpdir); + }); + after(() => app.close()); + after(() => server.close()); + beforeEach(() => app.mockCsrf()); + afterEach(mock.restore); + + it('should upload match file mode', async () => { + const form = formstream(); + form.field('foo', 'fengmk2').field('love', 'egg'); + form.file('file1', __filename, 'foooooooo.js'); + form.file('file2', __filename); + // will ignore empty file + form.buffer('file3', Buffer.from(''), '', 'application/octet-stream'); + form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.js')); + // other form fields + form.field('work', 'with Node.js'); + + const headers = form.headers(); + const res = await urllib.request(host + '/upload_file', { + method: 'POST', + headers, + stream: form, + }); + + assert(res.status === 200); + const data = JSON.parse(res.data); + assert.deepStrictEqual(data.body, { foo: 'fengmk2', love: 'egg', work: 'with Node.js' }); + assert(data.files.length === 3); + assert(data.files[0].field === 'file1'); + assert(data.files[0].filename === 'foooooooo.js'); + assert(data.files[0].encoding === '7bit'); + assert(data.files[0].mime === 'application/javascript'); + assert(data.files[0].filepath.startsWith(app.config.multipart.tmpdir)); + + assert(data.files[1].field === 'file2'); + assert(data.files[1].filename === 'stream-mode-with-filematch-glob.test.js'); + assert(data.files[1].encoding === '7bit'); + assert(data.files[1].mime === 'application/javascript'); + assert(data.files[1].filepath.startsWith(app.config.multipart.tmpdir)); + + assert(data.files[2].field === 'bigfile'); + assert(data.files[2].filename === 'bigfile.js'); + assert(data.files[2].encoding === '7bit'); + assert(data.files[2].mime === 'application/javascript'); + assert(data.files[2].filepath.startsWith(app.config.multipart.tmpdir)); + }); + + it('should upload not match file mode', async () => { + const form = formstream(); + form.field('foo', 'fengmk2').field('love', 'egg'); + form.file('file1', __filename, 'foooooooo.js'); + form.file('file2', __filename); + // will ignore empty file + form.buffer('file3', Buffer.from(''), '', 'application/octet-stream'); + form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.js')); + // other form fields + form.field('work', 'with Node.js'); + + const headers = form.headers(); + const res = await urllib.request(host + '/upload', { + method: 'POST', + headers, + stream: form, + }); + + assert(res.status === 200); + const data = JSON.parse(res.data); + assert.deepStrictEqual(data, { body: {} }); + }); +}); diff --git a/test/wrong-mode.test.js b/test/wrong-mode.test.js index 837f922..2a40281 100644 --- a/test/wrong-mode.test.js +++ b/test/wrong-mode.test.js @@ -29,16 +29,4 @@ describe('test/wrong-mode.test.js', () => { assert(err.message === '`fileModeMatch` options only work on stream mode, please remove it'); }); }); - - it('should start fail when using options.fileModeMatch is not RegExp', () => { - const app = mock.app({ - baseDir: 'apps/wrong-fileModeMatch-value', - }); - return app.ready() - .then(() => { - throw new Error('should not run this'); - }, err => { - assert(err.message === '`fileModeMatch` options should be an instance of RegExp'); - }); - }); });