formstream 1.4.0 → 1.5.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 ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2012 - 2014 fengmk2 <fengmk2@gmail.com>
4
+ Copyright (c) 2014-present node-modules and other contributors.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,10 +1,14 @@
1
1
  # formstream
2
2
 
3
3
  [![NPM version][npm-image]][npm-url]
4
+ [![CI](https://github.com/node-modules/formstream/actions/workflows/ci.yml/badge.svg)](https://github.com/node-modules/formstream/actions/workflows/ci.yml)
5
+ [![Test coverage][codecov-image]][codecov-url]
4
6
  [![npm download][download-image]][download-url]
5
7
 
6
8
  [npm-image]: https://img.shields.io/npm/v/formstream.svg?style=flat-square
7
9
  [npm-url]: https://npmjs.org/package/formstream
10
+ [codecov-image]: https://codecov.io/github/node-modules/formstream/coverage.svg?branch=master
11
+ [codecov-url]: https://codecov.io/github/node-modules/formstream?branch=master
8
12
  [download-image]: https://img.shields.io/npm/dm/formstream.svg?style=flat-square
9
13
  [download-url]: https://npmjs.org/package/formstream
10
14
 
@@ -13,7 +17,7 @@ A [multipart/form-data](http://tools.ietf.org/html/rfc2388) encoded stream, help
13
17
  ## Install
14
18
 
15
19
  ```bash
16
- $ npm install formstream
20
+ npm install formstream
17
21
  ```
18
22
 
19
23
  ## Quick Start
@@ -58,19 +62,44 @@ var formstream = require('formstream');
58
62
 
59
63
  var filepath = './logo.png';
60
64
  fs.stat(filepath, function (err, stat) {
61
- formstream().field('status', 'share picture')
62
- .field('access_token', 'your access token')
63
- .file('pic', filepath, 'logo.png', stat.size)
64
- .pipe(process.stdout); // your request stream
65
+ formstream()
66
+ .field('status', 'share picture')
67
+ .field('access_token', 'your access token')
68
+ .file('pic', filepath, 'logo.png', stat.size)
69
+ .pipe(process.stdout); // your request stream
70
+ });
71
+ ```
72
+
73
+ ### Set min chunk buffer size
74
+
75
+ Some web servers have a limit on the number of chunks, and you can set `minChunkSize` to ensure the size of chunk sent to the server.
76
+
77
+ ```js
78
+ var fs = require('fs');
79
+ var FormStream = require('formstream');
80
+
81
+ var filepath = './big-file.zip';
82
+ fs.stat(filepath, function (err, stat) {
83
+ new FormStream({
84
+ // send >= 2MB chunk buffer size to the server
85
+ minChunkSize: 1024 * 1024 * 2,
86
+ }).field('status', 'share file')
87
+ .field('access_token', 'your access token')
88
+ .file('file', filepath, 'big-file.zip', stat.size)
89
+ .pipe(process.stdout); // your request stream
65
90
  });
66
91
  ```
67
92
 
68
93
  ## API Doc
69
94
 
70
- ### formstream()
95
+ ### formstream([options])
71
96
 
72
97
  Create a form instance.
73
98
 
99
+ #### Arguments
100
+
101
+ - **options.minChunkSize** Number - min chunk size to emit data event
102
+
74
103
  #### Returns
75
104
 
76
105
  Form - form instance
@@ -140,7 +169,7 @@ Get headers for the request.
140
169
 
141
170
  #### Arguments
142
171
 
143
- - ***headers*** Object - Additional headers
172
+ - **headers** Object - Additional headers
144
173
 
145
174
  #### Example
146
175
 
@@ -173,29 +202,8 @@ See [Node.js Documentation](http://nodejs.org/api/stream.html#stream_event_end)
173
202
 
174
203
  ## License
175
204
 
176
- (The MIT License)
177
-
178
- Copyright (c) 2012 - 2014 fengmk2 <fengmk2@gmail.com>
179
- Copyright(c) node-modules and other contributors.
180
-
181
- Permission is hereby granted, free of charge, to any person obtaining
182
- a copy of this software and associated documentation files (the
183
- 'Software'), to deal in the Software without restriction, including
184
- without limitation the rights to use, copy, modify, merge, publish,
185
- distribute, sublicense, and/or sell copies of the Software, and to
186
- permit persons to whom the Software is furnished to do so, subject to
187
- the following conditions:
188
-
189
- The above copyright notice and this permission notice shall be
190
- included in all copies or substantial portions of the Software.
191
-
192
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
193
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
194
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
195
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
196
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
197
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
198
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
205
+ [MIT](LICENSE)
206
+
199
207
  <!-- GITCONTRIBUTOR_START -->
200
208
 
201
209
  ## Contributors
@@ -206,4 +214,4 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
206
214
 
207
215
  This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Wed May 15 2024 00:34:12 GMT+0800`.
208
216
 
209
- <!-- GITCONTRIBUTOR_END -->
217
+ <!-- GITCONTRIBUTOR_END -->
package/lib/formstream.js CHANGED
@@ -2,6 +2,7 @@
2
2
  * Form Data format:
3
3
  *
4
4
 
5
+ ```txt
5
6
  --FormStreamBoundary1349886663601\r\n
6
7
  Content-Disposition: form-data; name="foo"\r\n
7
8
  \r\n
@@ -15,19 +16,24 @@ Content-Type: application/json\r\n
15
16
  Content-Disposition: form-data; name="file"; filename="formstream.test.js"\r\n
16
17
  Content-Type: application/javascript\r\n
17
18
  \r\n
18
- <FILE-CONTENT>\r\n
19
+ <FILE-CONTENT-CHUNK-1>
20
+ ...
21
+ <FILE-CONTENT-CHUNK-N>
22
+ \r\n
19
23
  --FormStreamBoundary1349886663601\r\n
20
24
  Content-Disposition: form-data; name="pic"; filename="fawave.png"\r\n
21
25
  Content-Type: image/png\r\n
22
26
  \r\n
23
27
  <IMAGE-CONTENT>\r\n
24
28
  --FormStreamBoundary1349886663601--
29
+ ```
25
30
 
26
31
  *
27
32
  */
28
33
 
29
34
  'use strict';
30
35
 
36
+ var debug = require('util').debuglog('formstream');
31
37
  var Stream = require('stream');
32
38
  var parseStream = require('pause-stream');
33
39
  var util = require('util');
@@ -35,14 +41,15 @@ var mime = require('mime');
35
41
  var path = require('path');
36
42
  var fs = require('fs');
37
43
  var destroy = require('destroy');
44
+ var hex = require('node-hex');
38
45
 
39
46
  var PADDING = '--';
40
47
  var NEW_LINE = '\r\n';
41
48
  var NEW_LINE_BUFFER = Buffer.from(NEW_LINE);
42
49
 
43
- function FormStream() {
50
+ function FormStream(options) {
44
51
  if (!(this instanceof FormStream)) {
45
- return new FormStream();
52
+ return new FormStream(options);
46
53
  }
47
54
 
48
55
  FormStream.super_.call(this);
@@ -54,8 +61,10 @@ function FormStream() {
54
61
  this._contentLength = 0;
55
62
  this._isAllStreamSizeKnown = true;
56
63
  this._knownStreamSize = 0;
64
+ this._minChunkSize = options && options.minChunkSize || 0;
57
65
 
58
66
  this.isFormStream = true;
67
+ debug('start boundary\n%s', this._boundary);
59
68
  }
60
69
 
61
70
  util.inherits(FormStream, Stream);
@@ -83,12 +92,12 @@ FormStream.prototype.setTotalStreamSize = function (size) {
83
92
 
84
93
  for (var i = 0; i < this._streams.length; i++) {
85
94
  size += this._streams[i][0].length;
86
- size += NEW_LINE_BUFFER.length; // stream field end pedding size
95
+ size += NEW_LINE_BUFFER.length; // stream field end padding size
87
96
  }
88
97
 
89
98
  this._knownStreamSize = size;
90
99
  this._isAllStreamSizeKnown = true;
91
-
100
+ debug('set total size: %s', size);
92
101
  return this;
93
102
  };
94
103
 
@@ -99,7 +108,6 @@ FormStream.prototype.headers = function (options) {
99
108
 
100
109
  // calculate total stream size
101
110
  this._contentLength += this._knownStreamSize;
102
-
103
111
  // calculate length of end padding
104
112
  this._contentLength += this._endData.length;
105
113
 
@@ -113,19 +121,20 @@ FormStream.prototype.headers = function (options) {
113
121
  }
114
122
  }
115
123
 
124
+ debug('headers: %j', headers);
116
125
  return headers;
117
126
  };
118
127
 
119
128
  FormStream.prototype.file = function (name, filepath, filename, filesize) {
120
- var mimeType = mime.getType(filepath);
121
-
122
129
  if (typeof filename === 'number' && !filesize) {
123
130
  filesize = filename;
124
131
  filename = path.basename(filepath);
125
- } else if (!filename) {
132
+ }
133
+ if (!filename) {
126
134
  filename = path.basename(filepath);
127
135
  }
128
136
 
137
+ var mimeType = mime.getType(filename);
129
138
  var stream = fs.createReadStream(filepath);
130
139
 
131
140
  return this.stream(name, stream, filename, mimeType, filesize);
@@ -198,38 +207,37 @@ FormStream.prototype.buffer = function (name, buffer, filename, mimeType) {
198
207
 
199
208
  var leading = this._leading(disposition, mimeType);
200
209
 
201
- this._buffers.push([leading, buffer]);
202
-
203
210
  // plus buffer length to total content-length
204
- this._contentLength += leading.length;
205
- this._contentLength += buffer.length;
206
- this._contentLength += NEW_LINE_BUFFER.length;
211
+ var bufferSize = leading.length + buffer.length + NEW_LINE_BUFFER.length;
212
+ this._buffers.push(Buffer.concat([leading, buffer, NEW_LINE_BUFFER], bufferSize));
213
+ this._contentLength += bufferSize;
207
214
 
208
215
  process.nextTick(this.resume.bind(this));
209
-
216
+ if (debug.enabled) {
217
+ debug('new buffer field, content size: %d\n%s%s',
218
+ buffer.length, leading.toString(), hex(buffer));
219
+ }
210
220
  return this;
211
221
  };
212
222
 
213
223
  FormStream.prototype._leading = function (disposition, type) {
214
224
  var leading = [PADDING + this._boundary];
215
225
 
216
- var disps = [];
226
+ var dispositions = [];
217
227
 
218
228
  if (disposition) {
219
229
  for (var k in disposition) {
220
- disps.push(k + '="' + disposition[k] + '"');
230
+ dispositions.push(k + '="' + disposition[k] + '"');
221
231
  }
222
232
  }
223
233
 
224
- leading.push('Content-Disposition: form-data; ' + disps.join('; '));
225
-
234
+ leading.push('Content-Disposition: form-data; ' + dispositions.join('; '));
226
235
  if (type) {
227
236
  leading.push('Content-Type: ' + type);
228
237
  }
229
238
 
230
239
  leading.push('');
231
240
  leading.push('');
232
-
233
241
  return Buffer.from(leading.join(NEW_LINE));
234
242
  };
235
243
 
@@ -239,26 +247,101 @@ FormStream.prototype._emitBuffers = function () {
239
247
  }
240
248
 
241
249
  for (var i = 0; i < this._buffers.length; i++) {
242
- var item = this._buffers[i];
243
- this.emit('data', item[0]); // part leading
244
- this.emit('data', item[1]); // part content
245
- this.emit('data', NEW_LINE_BUFFER);
250
+ this.emit('data', this._buffers[i]);
246
251
  }
247
-
248
252
  this._buffers = [];
249
253
  };
250
254
 
251
255
  FormStream.prototype._emitStream = function (item) {
252
256
  var self = this;
253
- // item: [ fieldData, stream ]
254
- self.emit('data', item[0]);
257
+ // item: [ leading, stream ]
258
+ var streamSize = 0;
259
+ var chunkCount = 0;
260
+ const leading = item[0];
261
+ self.emit('data', leading);
262
+ chunkCount++;
263
+ if (debug.enabled) {
264
+ debug('new stream, chunk index %d\n%s', chunkCount, leading.toString());
265
+ }
255
266
 
256
267
  var stream = item[1];
257
268
  stream.on('data', function (data) {
258
269
  self.emit('data', data);
270
+ streamSize += leading.length;
271
+ chunkCount++;
272
+ if (debug.enabled) {
273
+ if (data.length > 512) {
274
+ debug('stream chunk, size %d, chunk index %d, stream size %d\n%s...... only show 512 bytes ......',
275
+ data.length, chunkCount, streamSize, hex(data.slice(0, 512)));
276
+ } else {
277
+ debug('stream chunk, size %d, chunk index %d, stream size %d\n%s',
278
+ data.length, chunkCount, streamSize, hex(data));
279
+ }
280
+ }
259
281
  });
260
282
  stream.on('end', function () {
261
283
  self.emit('data', NEW_LINE_BUFFER);
284
+ chunkCount++;
285
+ debug('stream end, chunk index %d, stream size %d', chunkCount, streamSize);
286
+ return process.nextTick(self.drain.bind(self));
287
+ });
288
+ stream.resume();
289
+ };
290
+
291
+ FormStream.prototype._emitStreamWithChunkSize = function (item, minChunkSize) {
292
+ var self = this;
293
+ // item: [ leading, stream ]
294
+ var streamSize = 0;
295
+ var chunkCount = 0;
296
+ var bufferSize = 0;
297
+ var buffers = [];
298
+ const leading = item[0];
299
+ buffers.push(leading);
300
+ bufferSize += leading.length;
301
+ if (debug.enabled) {
302
+ debug('new stream, with min chunk size: %d\n%s', minChunkSize, leading.toString());
303
+ }
304
+
305
+ var stream = item[1];
306
+ stream.on('data', function (data) {
307
+ if (typeof data === 'string') {
308
+ data = Buffer.from(data, 'utf-8');
309
+ }
310
+ buffers.push(data);
311
+ bufferSize += data.length;
312
+ streamSize += data.length;
313
+ debug('got stream data size %d, buffer size %d, stream size %d',
314
+ data.length, bufferSize, streamSize);
315
+ if (bufferSize >= minChunkSize) {
316
+ const chunk = Buffer.concat(buffers, bufferSize);
317
+ buffers = [];
318
+ bufferSize = 0;
319
+ self.emit('data', chunk);
320
+ chunkCount++;
321
+ if (debug.enabled) {
322
+ if (chunk.length > 512) {
323
+ debug('stream chunk, size %d, chunk index %d, stream size %d\n%s...... only show 512 bytes ......',
324
+ chunk.length, chunkCount, streamSize, hex(chunk.slice(0, 512)));
325
+ } else {
326
+ debug('stream chunk, size %d, chunk index %d, stream size %d\n%s',
327
+ chunk.length, chunkCount, streamSize, hex(chunk));
328
+ }
329
+ }
330
+ }
331
+ });
332
+ stream.on('end', function () {
333
+ buffers.push(NEW_LINE_BUFFER);
334
+ bufferSize += NEW_LINE_BUFFER.length;
335
+ const chunk = Buffer.concat(buffers, bufferSize);
336
+ self.emit('data', chunk);
337
+ chunkCount++;
338
+ if (chunk.length > 512) {
339
+ debug('stream end, size %d, chunk index %d, stream size %d\n%s...... only show 512 bytes ......',
340
+ chunk.length, chunkCount, streamSize, hex(chunk.slice(0, 512)));
341
+ } else {
342
+ debug('stream end, size %d, chunk index %d, stream size %d\n%s',
343
+ chunk.length, chunkCount, streamSize, hex(chunk));
344
+ }
262
345
  return process.nextTick(self.drain.bind(self));
263
346
  });
264
347
  stream.resume();
@@ -270,14 +353,22 @@ FormStream.prototype._emitEnd = function () {
270
353
  // --{boundary}--\r\n
271
354
  this.emit('data', this._endData);
272
355
  this.emit('end');
356
+ if (debug.enabled) {
357
+ debug('end boundary\n%s', this._endData.toString());
358
+ }
273
359
  };
274
360
 
275
361
  FormStream.prototype.drain = function () {
362
+ // debug('drain');
276
363
  this._emitBuffers();
277
364
 
278
365
  var item = this._streams.shift();
279
366
  if (item) {
280
- this._emitStream(item);
367
+ if (this._minChunkSize && this._minChunkSize > 0) {
368
+ this._emitStreamWithChunkSize(item, this._minChunkSize);
369
+ } else {
370
+ this._emitStream(item);
371
+ }
281
372
  } else {
282
373
  this._emitEnd();
283
374
  }
@@ -286,6 +377,7 @@ FormStream.prototype.drain = function () {
286
377
  };
287
378
 
288
379
  FormStream.prototype.resume = function () {
380
+ // debug('resume');
289
381
  this.paused = false;
290
382
 
291
383
  if (!this._draining) {
@@ -298,4 +390,5 @@ FormStream.prototype.resume = function () {
298
390
 
299
391
  FormStream.prototype.close = FormStream.prototype.destroy = function () {
300
392
  this.emit('destroy');
393
+ // debug('destroy or close');
301
394
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "formstream",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "A multipart/form-data encoded stream, helper for file upload.",
5
5
  "main": "lib/formstream.js",
6
6
  "types": "types/formstream.d.ts",
@@ -11,7 +11,7 @@
11
11
  "scripts": {
12
12
  "test": "egg-bin test",
13
13
  "cov": "egg-bin cov",
14
- "ci": "npm run lint && npm run tsd && npm run cov",
14
+ "ci": "npm run lint && npm run tsd && npm run cov && NODE_DEBUG=formstream npm run cov",
15
15
  "lint": "jshint .",
16
16
  "tsd": "tsd",
17
17
  "contributor": "git-contributor"
@@ -32,6 +32,7 @@
32
32
  "dependencies": {
33
33
  "destroy": "^1.0.4",
34
34
  "mime": "^2.5.2",
35
+ "node-hex": "^1.0.1",
35
36
  "pause-stream": "~0.0.11"
36
37
  },
37
38
  "devDependencies": {
@@ -64,9 +64,14 @@ declare class FormStream extends Stream {
64
64
  headers(additionalHeaders?: Record<string, any>): Record<string, any>
65
65
  }
66
66
 
67
+ interface FormStreamOptions {
68
+ /** min chunk size to emit data event */
69
+ minChunkSize?: number;
70
+ }
71
+
67
72
  declare const formStream: {
68
- new (): FormStream
69
- (): FormStream
73
+ new (options?: FormStreamOptions): FormStream
74
+ (options?: FormStreamOptions): FormStream
70
75
  }
71
76
 
72
77
  interface formStream extends FormStream {}