@eggjs/multipart 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +444 -0
- package/dist/commonjs/app/extend/context.d.ts +110 -0
- package/dist/commonjs/app/extend/context.js +279 -0
- package/dist/commonjs/app/middleware/multipart.d.ts +4 -0
- package/dist/commonjs/app/middleware/multipart.js +20 -0
- package/dist/commonjs/app/schedule/clean_tmpdir.d.ts +3 -0
- package/dist/commonjs/app/schedule/clean_tmpdir.js +58 -0
- package/dist/commonjs/app.d.ts +6 -0
- package/dist/commonjs/app.js +21 -0
- package/dist/commonjs/config/config.default.d.ts +98 -0
- package/dist/commonjs/config/config.default.js +31 -0
- package/dist/commonjs/index.d.ts +2 -0
- package/dist/commonjs/index.js +5 -0
- package/dist/commonjs/lib/LimitError.d.ts +5 -0
- package/dist/commonjs/lib/LimitError.js +16 -0
- package/dist/commonjs/lib/MultipartFileTooLargeError.d.ts +6 -0
- package/dist/commonjs/lib/MultipartFileTooLargeError.js +18 -0
- package/dist/commonjs/lib/utils.d.ts +4 -0
- package/dist/commonjs/lib/utils.js +93 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/esm/app/extend/context.d.ts +110 -0
- package/dist/esm/app/extend/context.js +273 -0
- package/dist/esm/app/middleware/multipart.d.ts +4 -0
- package/dist/esm/app/middleware/multipart.js +18 -0
- package/dist/esm/app/schedule/clean_tmpdir.d.ts +3 -0
- package/dist/esm/app/schedule/clean_tmpdir.js +53 -0
- package/dist/esm/app.d.ts +6 -0
- package/dist/esm/app.js +18 -0
- package/dist/esm/config/config.default.d.ts +98 -0
- package/dist/esm/config/config.default.js +26 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/lib/LimitError.d.ts +5 -0
- package/dist/esm/lib/LimitError.js +12 -0
- package/dist/esm/lib/MultipartFileTooLargeError.d.ts +6 -0
- package/dist/esm/lib/MultipartFileTooLargeError.js +14 -0
- package/dist/esm/lib/utils.d.ts +4 -0
- package/dist/esm/lib/utils.js +85 -0
- package/dist/esm/package.json +3 -0
- package/dist/package.json +4 -0
- package/package.json +103 -0
- package/src/app/extend/context.ts +368 -0
- package/src/app/middleware/multipart.ts +20 -0
- package/src/app/schedule/clean_tmpdir.ts +55 -0
- package/src/app.ts +20 -0
- package/src/config/config.default.ts +130 -0
- package/src/index.ts +2 -0
- package/src/lib/LimitError.ts +12 -0
- package/src/lib/MultipartFileTooLargeError.ts +14 -0
- package/src/lib/utils.ts +92 -0
- package/src/typings/index.d.ts +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-present Alibaba Group Holding Limited and other contributors.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# @eggjs/multipart
|
|
2
|
+
|
|
3
|
+
[![NPM version][npm-image]][npm-url]
|
|
4
|
+
[](https://github.com/eggjs/multipart/actions/workflows/nodejs.yml)
|
|
5
|
+
[![Test coverage][codecov-image]][codecov-url]
|
|
6
|
+
[![Known Vulnerabilities][snyk-image]][snyk-url]
|
|
7
|
+
[![npm download][download-image]][download-url]
|
|
8
|
+
[](https://makeapullrequest.com)
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
[npm-image]: https://img.shields.io/npm/v/@eggjs/multipart.svg?style=flat-square
|
|
12
|
+
[npm-url]: https://npmjs.org/package/@eggjs/multipart
|
|
13
|
+
[codecov-image]: https://codecov.io/github/eggjs/multipart/coverage.svg?branch=master
|
|
14
|
+
[codecov-url]: https://codecov.io/github/eggjs/multipart?branch=master
|
|
15
|
+
[snyk-image]: https://snyk.io/test/npm/@eggjs/multipart/badge.svg?style=flat-square
|
|
16
|
+
[snyk-url]: https://snyk.io/test/npm/@eggjs/multipart
|
|
17
|
+
[download-image]: https://img.shields.io/npm/dm/@eggjs/multipart.svg?style=flat-square
|
|
18
|
+
[download-url]: https://npmjs.org/package/@eggjs/multipart
|
|
19
|
+
|
|
20
|
+
Use [co-busboy](https://github.com/cojs/busboy) to upload file by streaming and
|
|
21
|
+
process it without save to disk(using the `stream` mode).
|
|
22
|
+
|
|
23
|
+
Just use `ctx.multipart()` to got file stream, then pass to image processing module such as `gm` or upload to cloud storage such as `oss`.
|
|
24
|
+
|
|
25
|
+
## Whitelist of file extensions
|
|
26
|
+
|
|
27
|
+
For security, if uploading file extension is not in white list, will response as `400 Bad request`.
|
|
28
|
+
|
|
29
|
+
Default Whitelist:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
const whitelist = [
|
|
33
|
+
// images
|
|
34
|
+
'.jpg', '.jpeg', // image/jpeg
|
|
35
|
+
'.png', // image/png, image/x-png
|
|
36
|
+
'.gif', // image/gif
|
|
37
|
+
'.bmp', // image/bmp
|
|
38
|
+
'.wbmp', // image/vnd.wap.wbmp
|
|
39
|
+
'.webp',
|
|
40
|
+
'.tif',
|
|
41
|
+
'.psd',
|
|
42
|
+
// text
|
|
43
|
+
'.svg',
|
|
44
|
+
'.js', '.jsx',
|
|
45
|
+
'.json',
|
|
46
|
+
'.css', '.less',
|
|
47
|
+
'.html', '.htm',
|
|
48
|
+
'.xml',
|
|
49
|
+
// tar
|
|
50
|
+
'.zip',
|
|
51
|
+
'.gz', '.tgz', '.gzip',
|
|
52
|
+
// video
|
|
53
|
+
'.mp3',
|
|
54
|
+
'.mp4',
|
|
55
|
+
'.avi',
|
|
56
|
+
];
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### fileSize
|
|
60
|
+
|
|
61
|
+
The default fileSize that multipart can accept is `10mb`. if you upload a large file, you should specify this config.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
// config/config.default.js
|
|
65
|
+
exports.multipart = {
|
|
66
|
+
fileSize: '50mb',
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Custom Config
|
|
71
|
+
|
|
72
|
+
Developer can custom additional file extensions:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// config/config.default.js
|
|
76
|
+
exports.multipart = {
|
|
77
|
+
// will append to whilelist
|
|
78
|
+
fileExtensions: [
|
|
79
|
+
'.foo',
|
|
80
|
+
'.apk',
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Can also **override** built-in whitelist, such as only allow png:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
// config/config.default.js
|
|
89
|
+
exports.multipart = {
|
|
90
|
+
whitelist: [
|
|
91
|
+
'.png',
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or by function:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
exports.multipart = {
|
|
100
|
+
whitelist: (filename) => [ '.png' ].includes(path.extname(filename) || '')
|
|
101
|
+
};
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Note: if define `whitelist`, then `fileExtensions` will be ignored.**
|
|
105
|
+
|
|
106
|
+
## Examples
|
|
107
|
+
|
|
108
|
+
More examples please follow:
|
|
109
|
+
|
|
110
|
+
- [Handle multipart request in `stream` mode](https://github.com/eggjs/examples/tree/master/multipart)
|
|
111
|
+
- [Handle multipart request in `file` mode](https://github.com/eggjs/examples/tree/master/multipart-file-mode)
|
|
112
|
+
|
|
113
|
+
## `file` mode: the easy way
|
|
114
|
+
|
|
115
|
+
If you don't know the [Node.js Stream](https://nodejs.org/dist/latest-v18.x/docs/api/stream.html) work,
|
|
116
|
+
maybe you should use the `file` mode to get started.
|
|
117
|
+
|
|
118
|
+
The usage very similar to [bodyParser](https://eggjs.org/en/basics/controller.html#body).
|
|
119
|
+
|
|
120
|
+
- `ctx.request.body`: Get all the multipart fields and values, except `file`.
|
|
121
|
+
- `ctx.request.files`: Contains all `file` from the multipart request, it's an Array object.
|
|
122
|
+
|
|
123
|
+
**WARNING: you should remove the temporary upload files after you use it**,
|
|
124
|
+
the `async ctx.cleanupRequestFiles()` method will be very helpful.
|
|
125
|
+
|
|
126
|
+
### Enable `file` mode on config
|
|
127
|
+
|
|
128
|
+
You need to set `config.multipart.mode = 'file'` to enable `file` mode:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
// config/config.default.js
|
|
132
|
+
exports.multipart = {
|
|
133
|
+
mode: 'file',
|
|
134
|
+
};
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
After `file` mode enable, egg will remove the old temporary files(don't include today's files) on `04:30 AM` every day by default.
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
config.multipart = {
|
|
141
|
+
mode: 'file',
|
|
142
|
+
tmpdir: path.join(os.tmpdir(), 'egg-multipart-tmp', appInfo.name),
|
|
143
|
+
cleanSchedule: {
|
|
144
|
+
// run tmpdir clean job on every day 04:30 am
|
|
145
|
+
// cron style see https://github.com/eggjs/egg-schedule#cron-style-scheduling
|
|
146
|
+
cron: '0 30 4 * * *',
|
|
147
|
+
disable: false,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Default will use the last field which has same name, if need the all fields value, please set `allowArrayField` in config.
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
// config/config.default.js
|
|
156
|
+
exports.multipart = {
|
|
157
|
+
mode: 'file',
|
|
158
|
+
allowArrayField: true,
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Upload One File
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
|
|
166
|
+
title: <input name="title" />
|
|
167
|
+
file: <input name="file" type="file" />
|
|
168
|
+
<button type="submit">Upload</button>
|
|
169
|
+
</form>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Controller which hanlder `POST /upload`:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
// app/controller/upload.js
|
|
176
|
+
const Controller = require('egg').Controller;
|
|
177
|
+
const fs = require('mz/fs');
|
|
178
|
+
|
|
179
|
+
module.exports = class extends Controller {
|
|
180
|
+
async upload() {
|
|
181
|
+
const { ctx } = this;
|
|
182
|
+
const file = ctx.request.files[0];
|
|
183
|
+
const name = 'egg-multipart-test/' + path.basename(file.filename);
|
|
184
|
+
let result;
|
|
185
|
+
try {
|
|
186
|
+
// process file or upload to cloud storage
|
|
187
|
+
result = await ctx.oss.put(name, file.filepath);
|
|
188
|
+
} finally {
|
|
189
|
+
// remove tmp files and don't block the request's response
|
|
190
|
+
// cleanupRequestFiles won't throw error even remove file io error happen
|
|
191
|
+
ctx.cleanupRequestFiles();
|
|
192
|
+
// remove tmp files before send response
|
|
193
|
+
// await ctx.cleanupRequestFiles();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
ctx.body = {
|
|
197
|
+
url: result.url,
|
|
198
|
+
// get all field values
|
|
199
|
+
requestBody: ctx.request.body,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Upload Multiple Files
|
|
206
|
+
|
|
207
|
+
```html
|
|
208
|
+
<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
|
|
209
|
+
title: <input name="title" />
|
|
210
|
+
file1: <input name="file1" type="file" />
|
|
211
|
+
file2: <input name="file2" type="file" />
|
|
212
|
+
<button type="submit">Upload</button>
|
|
213
|
+
</form>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Controller which hanlder `POST /upload`:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
// app/controller/upload.js
|
|
220
|
+
const Controller = require('egg').Controller;
|
|
221
|
+
const fs = require('mz/fs');
|
|
222
|
+
|
|
223
|
+
module.exports = class extends Controller {
|
|
224
|
+
async upload() {
|
|
225
|
+
const { ctx } = this;
|
|
226
|
+
console.log(ctx.request.body);
|
|
227
|
+
console.log('got %d files', ctx.request.files.length);
|
|
228
|
+
for (const file of ctx.request.files) {
|
|
229
|
+
console.log('field: ' + file.fieldname);
|
|
230
|
+
console.log('filename: ' + file.filename);
|
|
231
|
+
console.log('encoding: ' + file.encoding);
|
|
232
|
+
console.log('mime: ' + file.mime);
|
|
233
|
+
console.log('tmp filepath: ' + file.filepath);
|
|
234
|
+
let result;
|
|
235
|
+
try {
|
|
236
|
+
// process file or upload to cloud storage
|
|
237
|
+
result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);
|
|
238
|
+
} finally {
|
|
239
|
+
// remove tmp files and don't block the request's response
|
|
240
|
+
// cleanupRequestFiles won't throw error even remove file io error happen
|
|
241
|
+
ctx.cleanupRequestFiles([ file ]);
|
|
242
|
+
}
|
|
243
|
+
console.log(result);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## `stream` mode: the hard way
|
|
250
|
+
|
|
251
|
+
If you're well-known about know the Node.js Stream work, you should use the `stream` mode.
|
|
252
|
+
|
|
253
|
+
### Use with `for await...of`
|
|
254
|
+
|
|
255
|
+
```html
|
|
256
|
+
<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
|
|
257
|
+
title: <input name="title" />
|
|
258
|
+
file1: <input name="file1" type="file" />
|
|
259
|
+
file2: <input name="file2" type="file" />
|
|
260
|
+
<button type="submit">Upload</button>
|
|
261
|
+
</form>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Controller which hanlder `POST /upload`:
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
// app/controller/upload.js
|
|
268
|
+
const { Controller } = require('egg');
|
|
269
|
+
const fs = require('fs');
|
|
270
|
+
const stream = require('stream');
|
|
271
|
+
const util = require('util');
|
|
272
|
+
const { randomUUID } = require('crypto');
|
|
273
|
+
const pipeline = util.promisify(stream.pipeline);
|
|
274
|
+
|
|
275
|
+
module.exports = class UploadController extends Controller {
|
|
276
|
+
async upload() {
|
|
277
|
+
const parts = this.ctx.multipart();
|
|
278
|
+
const fields = {};
|
|
279
|
+
const files = {};
|
|
280
|
+
|
|
281
|
+
for await (const part of parts) {
|
|
282
|
+
if (Array.isArray(part)) {
|
|
283
|
+
// fields
|
|
284
|
+
console.log('field: ' + part[0]);
|
|
285
|
+
console.log('value: ' + part[1]);
|
|
286
|
+
} else {
|
|
287
|
+
// otherwise, it's a stream
|
|
288
|
+
const { filename, fieldname, encoding, mime } = part;
|
|
289
|
+
|
|
290
|
+
console.log('field: ' + fieldname);
|
|
291
|
+
console.log('filename: ' + filename);
|
|
292
|
+
console.log('encoding: ' + encoding);
|
|
293
|
+
console.log('mime: ' + mime);
|
|
294
|
+
|
|
295
|
+
// how to handler?
|
|
296
|
+
// 1. save to tmpdir with pipeline
|
|
297
|
+
// 2. or send to oss
|
|
298
|
+
// 3. or just consume it with another for await
|
|
299
|
+
|
|
300
|
+
// WARNING: You should almost never use the origin filename as it could contain malicious input.
|
|
301
|
+
const targetPath = path.join(os.tmpdir(), randomUUID() + path.extname(filename));
|
|
302
|
+
await pipeline(part, createWriteStream(targetPath)); // use `pipeline` not `pipe`
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
this.ctx.body = 'ok';
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Upload One File (DEPRECATED)
|
|
312
|
+
|
|
313
|
+
You can got upload stream by `ctx.getFileStream*()`.
|
|
314
|
+
|
|
315
|
+
```html
|
|
316
|
+
<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
|
|
317
|
+
title: <input name="title" />
|
|
318
|
+
file: <input name="file" type="file" />
|
|
319
|
+
<button type="submit">Upload</button>
|
|
320
|
+
</form>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Controller which handler `POST /upload`:
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
// app/controller/upload.js
|
|
327
|
+
const path = require('node:path');
|
|
328
|
+
const { sendToWormhole } = require('stream-wormhole');
|
|
329
|
+
const { Controller } = require('egg');
|
|
330
|
+
|
|
331
|
+
module.exports = class extends Controller {
|
|
332
|
+
async upload() {
|
|
333
|
+
const { ctx } = this;
|
|
334
|
+
// file not exists will response 400 error
|
|
335
|
+
const stream = await ctx.getFileStream();
|
|
336
|
+
const name = 'egg-multipart-test/' + path.basename(stream.filename);
|
|
337
|
+
// process file or upload to cloud storage
|
|
338
|
+
const result = await ctx.oss.put(name, stream);
|
|
339
|
+
|
|
340
|
+
ctx.body = {
|
|
341
|
+
url: result.url,
|
|
342
|
+
// process form fields by `stream.fields`
|
|
343
|
+
fields: stream.fields,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async uploadNotRequiredFile() {
|
|
348
|
+
const { ctx } = this;
|
|
349
|
+
// file not required
|
|
350
|
+
const stream = await ctx.getFileStream({ requireFile: false });
|
|
351
|
+
let result;
|
|
352
|
+
if (stream.filename) {
|
|
353
|
+
const name = 'egg-multipart-test/' + path.basename(stream.filename);
|
|
354
|
+
// process file or upload to cloud storage
|
|
355
|
+
const result = await ctx.oss.put(name, stream);
|
|
356
|
+
} else {
|
|
357
|
+
// must consume the empty stream
|
|
358
|
+
await sendToWormhole(stream);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
ctx.body = {
|
|
362
|
+
url: result && result.url,
|
|
363
|
+
// process form fields by `stream.fields`
|
|
364
|
+
fields: stream.fields,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Upload Multiple Files (DEPRECATED)
|
|
371
|
+
|
|
372
|
+
```html
|
|
373
|
+
<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
|
|
374
|
+
title: <input name="title" />
|
|
375
|
+
file1: <input name="file1" type="file" />
|
|
376
|
+
file2: <input name="file2" type="file" />
|
|
377
|
+
<button type="submit">Upload</button>
|
|
378
|
+
</form>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Controller which hanlder `POST /upload`:
|
|
382
|
+
|
|
383
|
+
```js
|
|
384
|
+
// app/controller/upload.js
|
|
385
|
+
const Controller = require('egg').Controller;
|
|
386
|
+
|
|
387
|
+
module.exports = class extends Controller {
|
|
388
|
+
async upload() {
|
|
389
|
+
const { ctx } = this;
|
|
390
|
+
const parts = ctx.multipart();
|
|
391
|
+
let part;
|
|
392
|
+
while ((part = await parts()) != null) {
|
|
393
|
+
if (part.length) {
|
|
394
|
+
// arrays are busboy fields
|
|
395
|
+
console.log('field: ' + part[0]);
|
|
396
|
+
console.log('value: ' + part[1]);
|
|
397
|
+
console.log('valueTruncated: ' + part[2]);
|
|
398
|
+
console.log('fieldnameTruncated: ' + part[3]);
|
|
399
|
+
} else {
|
|
400
|
+
if (!part.filename) {
|
|
401
|
+
// user click `upload` before choose a file,
|
|
402
|
+
// `part` will be file stream, but `part.filename` is empty
|
|
403
|
+
// must handler this, such as log error.
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
// otherwise, it's a stream
|
|
407
|
+
console.log('field: ' + part.fieldname);
|
|
408
|
+
console.log('filename: ' + part.filename);
|
|
409
|
+
console.log('encoding: ' + part.encoding);
|
|
410
|
+
console.log('mime: ' + part.mime);
|
|
411
|
+
const result = await ctx.oss.put('egg-multipart-test/' + part.filename, part);
|
|
412
|
+
console.log(result);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
console.log('and we are done parsing the form!');
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Support `file` and `stream` mode in the same time
|
|
421
|
+
|
|
422
|
+
If the default `mode` is `stream`, use the `fileModeMatch` options to match the request urls switch to `file` mode.
|
|
423
|
+
|
|
424
|
+
```js
|
|
425
|
+
config.multipart = {
|
|
426
|
+
mode: 'stream',
|
|
427
|
+
// let POST /upload_file request use the file mode, other requests use the stream mode.
|
|
428
|
+
fileModeMatch: /^\/upload_file$/,
|
|
429
|
+
// or glob
|
|
430
|
+
// fileModeMatch: '/upload_file',
|
|
431
|
+
};
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
NOTICE: `fileModeMatch` options only work on `stream` mode.
|
|
435
|
+
|
|
436
|
+
## License
|
|
437
|
+
|
|
438
|
+
[MIT](LICENSE)
|
|
439
|
+
|
|
440
|
+
## Contributors
|
|
441
|
+
|
|
442
|
+
[](https://github.com/eggjs/multipart/graphs/contributors)
|
|
443
|
+
|
|
444
|
+
Made with [contributors-img](https://contrib.rocks).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { Context } from '@eggjs/core';
|
|
3
|
+
export interface EggFile {
|
|
4
|
+
field: string;
|
|
5
|
+
filename: string;
|
|
6
|
+
encoding: string;
|
|
7
|
+
mime: string;
|
|
8
|
+
filepath: string;
|
|
9
|
+
}
|
|
10
|
+
export interface MultipartFileStream extends Readable {
|
|
11
|
+
fields: Record<string, any>;
|
|
12
|
+
filename: string;
|
|
13
|
+
fieldname: string;
|
|
14
|
+
mime: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
transferEncoding: string;
|
|
17
|
+
encoding: string;
|
|
18
|
+
truncated: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface MultipartOptions {
|
|
21
|
+
autoFields?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* required file submit, default is true
|
|
24
|
+
*/
|
|
25
|
+
requireFile?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* default charset encoding
|
|
28
|
+
*/
|
|
29
|
+
defaultCharset?: string;
|
|
30
|
+
/**
|
|
31
|
+
* compatible with defaultCharset
|
|
32
|
+
* @deprecated use `defaultCharset` instead
|
|
33
|
+
*/
|
|
34
|
+
defCharset?: string;
|
|
35
|
+
defaultParamCharset?: string;
|
|
36
|
+
/**
|
|
37
|
+
* compatible with defaultParamCharset
|
|
38
|
+
* @deprecated use `defaultParamCharset` instead
|
|
39
|
+
*/
|
|
40
|
+
defParamCharset?: string;
|
|
41
|
+
limits?: {
|
|
42
|
+
fieldNameSize?: number;
|
|
43
|
+
fieldSize?: number;
|
|
44
|
+
fields?: number;
|
|
45
|
+
fileSize?: number;
|
|
46
|
+
files?: number;
|
|
47
|
+
parts?: number;
|
|
48
|
+
headerPairs?: number;
|
|
49
|
+
};
|
|
50
|
+
checkFile?(fieldname: string, file: any, filename: string, encoding: string, mimetype: string): void | Error;
|
|
51
|
+
}
|
|
52
|
+
export default class MultipartContext extends Context {
|
|
53
|
+
/**
|
|
54
|
+
* create multipart.parts instance, to get separated files.
|
|
55
|
+
* @function Context#multipart
|
|
56
|
+
* @param {Object} [options] - override default multipart configurations
|
|
57
|
+
* - {Boolean} options.autoFields
|
|
58
|
+
* - {String} options.defaultCharset
|
|
59
|
+
* - {String} options.defaultParamCharset
|
|
60
|
+
* - {Object} options.limits
|
|
61
|
+
* - {Function} options.checkFile
|
|
62
|
+
* @return {Yieldable | AsyncIterable<Yieldable>} parts
|
|
63
|
+
*/
|
|
64
|
+
multipart(options?: MultipartOptions): AsyncIterable<MultipartFileStream>;
|
|
65
|
+
/**
|
|
66
|
+
* save request multipart data and files to `ctx.request`
|
|
67
|
+
* @function Context#saveRequestFiles
|
|
68
|
+
* @param {Object} options - { limits, checkFile, ... }
|
|
69
|
+
*/
|
|
70
|
+
saveRequestFiles(options?: MultipartOptions): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* get upload file stream
|
|
73
|
+
* @example
|
|
74
|
+
* ```js
|
|
75
|
+
* const stream = await ctx.getFileStream();
|
|
76
|
+
* // get other fields
|
|
77
|
+
* console.log(stream.fields);
|
|
78
|
+
* ```
|
|
79
|
+
* @function Context#getFileStream
|
|
80
|
+
* @param {Object} options
|
|
81
|
+
* - {Boolean} options.requireFile - required file submit, default is true
|
|
82
|
+
* - {String} options.defaultCharset
|
|
83
|
+
* - {String} options.defaultParamCharset
|
|
84
|
+
* - {Object} options.limits
|
|
85
|
+
* - {Function} options.checkFile
|
|
86
|
+
* @return {ReadStream} stream
|
|
87
|
+
* @since 1.0.0
|
|
88
|
+
* @deprecated Not safe enough, use `ctx.multipart()` instead
|
|
89
|
+
*/
|
|
90
|
+
getFileStream(options?: MultipartOptions): Promise<MultipartFileStream>;
|
|
91
|
+
/**
|
|
92
|
+
* clean up request tmp files helper
|
|
93
|
+
* @function Context#cleanupRequestFiles
|
|
94
|
+
* @param {Array<String>} [files] - file paths need to cleanup, default is `ctx.request.files`.
|
|
95
|
+
*/
|
|
96
|
+
cleanupRequestFiles(files?: EggFile[]): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
declare module '@eggjs/core' {
|
|
99
|
+
interface Request {
|
|
100
|
+
/**
|
|
101
|
+
* Files Object Array
|
|
102
|
+
*/
|
|
103
|
+
files?: EggFile[];
|
|
104
|
+
}
|
|
105
|
+
interface Context {
|
|
106
|
+
saveRequestFiles(options?: MultipartOptions): Promise<void>;
|
|
107
|
+
getFileStream(options?: MultipartOptions): Promise<MultipartFileStream>;
|
|
108
|
+
cleanupRequestFiles(files?: EggFile[]): Promise<void>;
|
|
109
|
+
}
|
|
110
|
+
}
|