-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced `WebFormData` as an alternative export for `FormData`, enhancing compatibility and clarity. - Added a new `BufferStream` class to manage data chunk buffering efficiently. - **Tests** - Implemented a new test suite for validating data posting using `BufferStream` with `WebFormData`. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
- Loading branch information
Showing
3 changed files
with
116 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { Transform } from 'node:stream'; | ||
|
||
const BUF_SIZE = 1024 * 1024; | ||
|
||
export class BufferStream extends Transform { | ||
private buf: Buffer; | ||
private offset: number; | ||
|
||
constructor(options?: any) { | ||
super(options); | ||
this.realloc(); | ||
} | ||
|
||
realloc() { | ||
this.buf = Buffer.alloc(BUF_SIZE); | ||
this.offset = 0; | ||
} | ||
|
||
_transform(chunk: Buffer, _: any, callback: any) { | ||
const currentLength = this.offset; | ||
const chunkSize = chunk.length; | ||
const newSize = currentLength + chunkSize; | ||
// 缓冲区未满 | ||
// - 向缓冲区写入 | ||
if (newSize < BUF_SIZE) { | ||
chunk.copy(this.buf, currentLength); | ||
this.offset += chunkSize; | ||
return callback(); | ||
} | ||
|
||
// 缓冲区正好满 | ||
// - 拷贝到缓冲区以后, 将 chunk 返回 | ||
// - 刷新缓冲区 | ||
if (newSize === BUF_SIZE) { | ||
chunk.copy(this.buf, currentLength); | ||
const writeChunk = this.buf; | ||
this.realloc(); | ||
return callback(null, writeChunk); | ||
} | ||
|
||
// 超过缓冲区大小 | ||
// - 拷贝到缓冲区以后, 将 chunk 返回 | ||
// - 刷新缓冲区 | ||
// - 将超出的部分拷贝到新的缓冲区中 | ||
const copyLength = BUF_SIZE - currentLength; | ||
const remainLength = chunkSize - copyLength; | ||
chunk.copy(this.buf, currentLength, 0, copyLength); | ||
const writeChunk = this.buf; | ||
this.push(writeChunk); | ||
this.realloc(); | ||
|
||
if (remainLength > BUF_SIZE) { | ||
// 特殊情况: 给了一个超大 chunk | ||
// 直接将这个 chunk 返回,没必要缓冲了 | ||
this.push(chunk.slice(copyLength)); | ||
} else { | ||
chunk.copy(this.buf, 0, copyLength); | ||
this.offset = remainLength; | ||
} | ||
return callback(null); | ||
} | ||
|
||
_flush(callback: any) { | ||
if (this.offset) { | ||
const chunk = Buffer.alloc(this.offset); | ||
this.buf.copy(chunk); | ||
this.push(chunk); | ||
this.offset = 0; | ||
} | ||
callback(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { strict as assert } from 'node:assert'; | ||
import { createReadStream } from 'node:fs'; | ||
import { basename } from 'node:path'; | ||
import { describe, it, beforeAll, afterAll } from 'vitest'; | ||
import { HttpClient, WebFormData } from '../src/index.js'; | ||
import { startServer } from './fixtures/server.js'; | ||
import { BufferStream } from './fixtures/BufferStream.js'; | ||
|
||
describe('formData-with-BufferStream.test.ts', () => { | ||
let close: any; | ||
let _url: string; | ||
beforeAll(async () => { | ||
const { closeServer, url } = await startServer(); | ||
close = closeServer; | ||
_url = url; | ||
}); | ||
|
||
afterAll(async () => { | ||
await close(); | ||
}); | ||
|
||
it('should post with BufferStream', async () => { | ||
const fileStream = createReadStream(__filename); | ||
const bufferStream = new BufferStream(); | ||
fileStream.pipe(bufferStream); | ||
const formData = new WebFormData(); | ||
const fileName = basename(__filename); | ||
formData.append('fileBufferStream', bufferStream, fileName); | ||
formData.append('foo', 'bar'); | ||
|
||
const httpClient = new HttpClient(); | ||
const response = await httpClient.request(`${_url}multipart`, { | ||
method: 'POST', | ||
content: formData, | ||
headers: formData.getHeaders(), | ||
dataType: 'json', | ||
}); | ||
assert.equal(response.status, 200); | ||
// console.log(response.data); | ||
assert.equal(response.data.files.fileBufferStream.filename, 'formData-with-BufferStream.test.ts'); | ||
assert.deepEqual(response.data.form, { foo: 'bar' }); | ||
}); | ||
}); |