Skip to content

Commit

Permalink
feat: fileModeMatch support glob with egg-path-matching (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
atian25 committed Aug 9, 2019
1 parent 8d6ce3c commit a1fcdab
Show file tree
Hide file tree
Showing 15 changed files with 159 additions and 17 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
```

Expand Down
2 changes: 0 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';

const assert = require('assert');
const path = require('path');
const bytes = require('humanize-bytes');

Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion app/middleware/multipart.js
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
2 changes: 1 addition & 1 deletion config/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 9 additions & 0 deletions test/fixtures/apps/fileModeMatch-glob/app/controller/save.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

module.exports = async ctx => {
await ctx.saveRequestFiles();
ctx.body = {
body: ctx.request.body,
files: ctx.request.files,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';

module.exports = async ctx => {
ctx.body = {
body: ctx.request.body,
files: ctx.request.files,
};
};
7 changes: 7 additions & 0 deletions test/fixtures/apps/fileModeMatch-glob/app/router.js
Original file line number Diff line number Diff line change
@@ -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);
};
8 changes: 8 additions & 0 deletions test/fixtures/apps/fileModeMatch-glob/app/views/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<form method="POST" action="/upload_file?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
title: <input name="title" />
file1: <input name="file1" type="file" />
file2: <input name="file2" type="file" />
file3: <input name="file3" type="file" />
other: <input name="other" />
<button type="submit">上传</button>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';

exports.multipart = {
mode: 'stream',
fileModeMatch: '/upload_file'
};

exports.keys = 'multipart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';

exports.logger = {
consoleLevel: 'NONE',
coreLogger: {
// consoleLevel: 'DEBUG',
},
};
3 changes: 3 additions & 0 deletions test/fixtures/apps/fileModeMatch-glob/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "multipart-file-mode-demo"
}
95 changes: 95 additions & 0 deletions test/stream-mode-with-filematch-glob.test.js
Original file line number Diff line number Diff line change
@@ -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: {} });
});
});
12 changes: 0 additions & 12 deletions test/wrong-mode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
});

0 comments on commit a1fcdab

Please sign in to comment.