Skip to content

Commit d474eec

Browse files
authored
feat: support custom pathToRegexpModule (#66)
part of eggjs/core#290 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Dependencies** - Updated `egg-path-matching` to version 1.2.0 - Added `path-to-regexp-v8` dependency - **Middleware** - Enhanced multipart file upload middleware with additional application context configuration - **Testing** - Improved file upload test cases - Updated error message assertions for more flexible matching - Added new test scenarios for file upload endpoints - **Configuration** - Updated file upload handling configuration - Added new routing and controller logic for file uploads <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent b2a9662 commit d474eec

File tree

14 files changed

+242
-13
lines changed

14 files changed

+242
-13
lines changed

app/middleware/multipart.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
'use strict';
2-
31
const pathMatching = require('egg-path-matching');
42

5-
module.exports = options => {
3+
module.exports = (options, app) => {
64
// normalize
7-
const matchFn = options.fileModeMatch && pathMatching({ match: options.fileModeMatch });
5+
const matchFn = options.fileModeMatch && pathMatching({
6+
match: options.fileModeMatch,
7+
pathToRegexpModule: app.options.pathToRegexpModule,
8+
});
89

910
return async function multipart(ctx, next) {
1011
if (!ctx.is('multipart')) return next();

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"dependencies": {
4747
"co-busboy": "^2.0.0",
4848
"dayjs": "^1.11.5",
49-
"egg-path-matching": "^1.0.1",
49+
"egg-path-matching": "^1.2.0",
5050
"humanize-bytes": "^1.0.1"
5151
},
5252
"devDependencies": {
@@ -62,6 +62,7 @@
6262
"is-type-of": "^1.2.1",
6363
"typescript": "5",
6464
"urllib": "3",
65-
"stream-wormhole": "^2.0.1"
65+
"stream-wormhole": "^2.0.1",
66+
"path-to-regexp-v8": "npm:path-to-regexp@8"
6667
}
6768
}

test/dynamic-option.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ describe('test/dynamic-option.test.js', () => {
4141
});
4242

4343
assert(res.status === 413);
44-
assert(res.data.toString().includes('Request_fileSize_limitError: Reach fileSize limit'));
44+
assert.match(res.data.toString(), /Error: Reach fileSize limit/);
4545
});
4646
});
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
const assert = require('assert');
2+
const formstream = require('formstream');
3+
const urllib = require('urllib');
4+
const path = require('path');
5+
const mock = require('egg-mock');
6+
const fs = require('fs').promises;
7+
8+
describe('test/enable-pathToRegexpModule.test.js', () => {
9+
let app;
10+
let server;
11+
let host;
12+
before(() => {
13+
app = mock.app({
14+
baseDir: 'apps/fileModeMatch-glob-with-pathToRegexpModule',
15+
pathToRegexpModule: require.resolve('path-to-regexp-v8'),
16+
});
17+
return app.ready();
18+
});
19+
before(() => {
20+
server = app.listen();
21+
host = 'http://127.0.0.1:' + server.address().port;
22+
});
23+
after(() => {
24+
return fs.rm(app.config.multipart.tmpdir, { force: true, recursive: true });
25+
});
26+
after(() => app.close());
27+
after(() => server.close());
28+
beforeEach(() => app.mockCsrf());
29+
afterEach(mock.restore);
30+
31+
it('should upload match file mode work on /upload_file', async () => {
32+
const form = formstream();
33+
form.field('foo', 'fengmk2').field('love', 'egg');
34+
form.file('file1', __filename, 'foooooooo.js');
35+
form.file('file2', __filename);
36+
// will ignore empty file
37+
form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');
38+
form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.js'));
39+
// other form fields
40+
form.field('work', 'with Node.js');
41+
42+
const headers = form.headers();
43+
const res = await urllib.request(host + '/upload_file', {
44+
method: 'POST',
45+
headers,
46+
stream: form,
47+
});
48+
49+
assert(res.status === 200);
50+
const data = JSON.parse(res.data);
51+
assert.deepStrictEqual(data.body, { foo: 'fengmk2', love: 'egg', work: 'with Node.js' });
52+
assert(data.files.length === 3);
53+
assert(data.files[0].field === 'file1');
54+
assert(data.files[0].filename === 'foooooooo.js');
55+
assert(data.files[0].encoding === '7bit');
56+
assert(data.files[0].mime === 'application/javascript');
57+
assert(data.files[0].filepath.startsWith(app.config.multipart.tmpdir));
58+
59+
assert(data.files[1].field === 'file2');
60+
assert(data.files[1].filename === 'enable-pathToRegexpModule.test.js');
61+
assert(data.files[1].encoding === '7bit');
62+
assert(data.files[1].mime === 'application/javascript');
63+
assert(data.files[1].filepath.startsWith(app.config.multipart.tmpdir));
64+
65+
assert(data.files[2].field === 'bigfile');
66+
assert(data.files[2].filename === 'bigfile.js');
67+
assert(data.files[2].encoding === '7bit');
68+
assert(data.files[2].mime === 'application/javascript');
69+
assert(data.files[2].filepath.startsWith(app.config.multipart.tmpdir));
70+
});
71+
72+
it('should upload match file mode work on /upload_file/*', async () => {
73+
const form = formstream();
74+
form.field('foo', 'fengmk2').field('love', 'egg');
75+
form.file('file1', __filename, 'foooooooo.js');
76+
form.file('file2', __filename);
77+
// will ignore empty file
78+
form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');
79+
form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.js'));
80+
// other form fields
81+
form.field('work', 'with Node.js');
82+
83+
const headers = form.headers();
84+
const res = await urllib.request(host + '/upload_file/foo', {
85+
method: 'POST',
86+
headers,
87+
stream: form,
88+
});
89+
90+
assert.equal(res.status, 200);
91+
const data = JSON.parse(res.data);
92+
assert.deepStrictEqual(data.body, { foo: 'fengmk2', love: 'egg', work: 'with Node.js' });
93+
assert(data.files.length === 3);
94+
assert(data.files[0].field === 'file1');
95+
assert(data.files[0].filename === 'foooooooo.js');
96+
assert(data.files[0].encoding === '7bit');
97+
assert(data.files[0].mime === 'application/javascript');
98+
assert(data.files[0].filepath.startsWith(app.config.multipart.tmpdir));
99+
100+
assert(data.files[1].field === 'file2');
101+
assert(data.files[1].filename === 'enable-pathToRegexpModule.test.js');
102+
assert(data.files[1].encoding === '7bit');
103+
assert(data.files[1].mime === 'application/javascript');
104+
assert(data.files[1].filepath.startsWith(app.config.multipart.tmpdir));
105+
106+
assert(data.files[2].field === 'bigfile');
107+
assert(data.files[2].filename === 'bigfile.js');
108+
assert(data.files[2].encoding === '7bit');
109+
assert(data.files[2].mime === 'application/javascript');
110+
assert(data.files[2].filepath.startsWith(app.config.multipart.tmpdir));
111+
});
112+
113+
it('should upload not match file mode', async () => {
114+
const form = formstream();
115+
form.field('foo', 'fengmk2').field('love', 'egg');
116+
form.file('file1', __filename, 'foooooooo.js');
117+
form.file('file2', __filename);
118+
// will ignore empty file
119+
form.buffer('file3', Buffer.from(''), '', 'application/octet-stream');
120+
form.file('bigfile', path.join(__dirname, 'fixtures', 'bigfile.js'));
121+
// other form fields
122+
form.field('work', 'with Node.js');
123+
124+
const headers = form.headers();
125+
const res = await urllib.request(host + '/upload', {
126+
method: 'POST',
127+
headers,
128+
stream: form,
129+
});
130+
131+
assert(res.status === 200);
132+
const data = JSON.parse(res.data);
133+
assert.deepStrictEqual(data, { body: {} });
134+
});
135+
});

test/file-mode.test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ describe('test/file-mode.test.js', () => {
187187
});
188188

189189
assert(res.status === 413);
190-
assert(res.data.toString().includes('Request_fields_limitError: Reach fields limit'));
190+
assert.match(res.data.toString(), /Error: Reach fields limit/);
191191
});
192192

193193
it('should throw error when request files limit', async () => {
@@ -205,7 +205,7 @@ describe('test/file-mode.test.js', () => {
205205
});
206206

207207
assert(res.status === 413);
208-
assert(res.data.toString().includes('Request_files_limitError: Reach files limit'));
208+
assert.match(res.data.toString(), /Error: Reach files limit/);
209209
});
210210

211211
it('should throw error when request field size limit', async () => {
@@ -220,7 +220,7 @@ describe('test/file-mode.test.js', () => {
220220
});
221221

222222
assert(res.status === 413);
223-
assert(res.data.toString().includes('Request_fieldSize_limitError: Reach fieldSize limit'));
223+
assert.match(res.data.toString(), /Error: Reach fieldSize limit/);
224224
});
225225

226226
// fieldNameSize is TODO on busboy
@@ -237,7 +237,7 @@ describe('test/file-mode.test.js', () => {
237237
});
238238

239239
assert(res.status === 413);
240-
assert(res.data.toString().includes('Request_fieldSize_limitError: Reach fieldSize limit'));
240+
assert.match(res.data.toString(), /Error: Reach fieldSize limit/);
241241
});
242242

243243
it('should throw error when request file size limit', async () => {
@@ -256,7 +256,7 @@ describe('test/file-mode.test.js', () => {
256256
});
257257

258258
assert(res.status === 413);
259-
assert(res.data.toString().includes('Request_fileSize_limitError: Reach fileSize limit'));
259+
assert.match(res.data.toString(), /Error: Reach fileSize limit/);
260260
});
261261

262262
it('should throw error when file name invalid', async () => {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
module.exports = async ctx => {
4+
await ctx.saveRequestFiles();
5+
ctx.body = {
6+
body: ctx.request.body,
7+
files: ctx.request.files,
8+
};
9+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
3+
module.exports = async ctx => {
4+
ctx.body = {
5+
body: ctx.request.body,
6+
files: ctx.request.files,
7+
};
8+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
3+
module.exports = app => {
4+
app.post('/upload', app.controller.upload);
5+
app.post('/upload_file', app.controller.upload);
6+
app.post('/upload_file/foo', app.controller.upload);
7+
app.post('/save', app.controller.save);
8+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<form method="POST" action="/upload_file?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
2+
title: <input name="title" />
3+
file1: <input name="file1" type="file" />
4+
file2: <input name="file2" type="file" />
5+
file3: <input name="file3" type="file" />
6+
other: <input name="other" />
7+
<button type="submit">上传</button>
8+
</form>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
exports.multipart = {
2+
mode: 'stream',
3+
fileModeMatch: '/upload_file{/:paths}'
4+
};
5+
6+
exports.keys = 'multipart';

0 commit comments

Comments
 (0)