Skip to content

Instantly share code, notes, and snippets.

@liyu1981
Created March 5, 2025 09:37
Show Gist options
  • Save liyu1981/e85c9ace0e9304350fb3b5c13844062d to your computer and use it in GitHub Desktop.
Save liyu1981/e85c9ace0e9304350fb3b5c13844062d to your computer and use it in GitHub Desktop.
example of how to stream file uploading with undici
{
"name": "undici_upload_example",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "NODE_ENV=test node -r tsx/cjs --test undiciUpload.test.ts"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"form-data": "^4.0.2",
"undici": "^7.4.0"
},
"devDependencies": {
"ts-node": "^10.9.2",
"tsx": "^4.19.3"
}
}
import { streamUploadWithUndici } from "./undiciUpload";
import { createServer } from "node:http";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
import { Buffer } from "node:buffer";
import { describe, it } from "node:test";
import assert from "node:assert";
function createLocalHttpServerForUpload(
saveUploadedFileFn: (fileBuffer: Buffer) => void
) {
let port: number;
let s: any;
const RetryMax = 5;
let retry = 0;
while (retry < RetryMax) {
try {
port = Math.floor(Math.random() * 40000 + 10000);
s = createServer(async (req, res) => {
if (req.method === "POST" && req.url === "/upload") {
const chunks: any[] = [];
try {
await pipeline(req, async function* (source) {
for await (const chunk of source) {
chunks.push(chunk);
}
yield;
});
const fileBuffer = Buffer.concat(chunks);
saveUploadedFileFn(fileBuffer);
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("File received in memory\n");
} catch (error) {
console.error("Upload Error:", error);
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("File upload failed\n");
}
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found\n");
}
});
s.listen(port);
return [s, port];
} catch (_e) {
retry += 1;
}
}
throw new Error("failed to create local server with random port");
}
describe("streamUploadWithUndici", () => {
it("should upload file to server", async () => {
let uploadedFileBuffer: Buffer = Buffer.from("");
const [server, port] = createLocalHttpServerForUpload((b) => {
uploadedFileBuffer = b;
});
const testUploadURL = `http://localhost:${port}/upload`;
const sampleReadable = Readable.from(
`this is a sample file content for testing uploading with undici`
);
const res = await streamUploadWithUndici(testUploadURL, sampleReadable);
assert(res.statusCode === 200);
assert(uploadedFileBuffer.length === 262);
const contents = uploadedFileBuffer.toString().split("\r\n");
const expectedContents = contents.slice(1, -2).join("\r\n");
assert.equal(expectedContents, 'Content-Disposition: form-data; name="file"\r\nContent-Type: application/octet-stream\r\n\r\nthis is a sample file content for testing uploading with undici');
server.close();
});
});
import FormData from "form-data";
import Readable from "node:stream";
import {request} from "undici";
export async function streamUploadWithUndici(url: string, streamInput: Readable) {
// The key here is to use FormData from pkg "form-data" instead of the one
// from "undici". The latter doesn't support streaming.
const formData = new FormData();
formData.append("file", streamInput);
const res = await request(url, {
method: "POST",
body: formData
});
return res;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment