Skip to content

Commit 07dc367

Browse files
authored
fix: use Uint8Array instead of Buffer (#1146)
1 parent 9a9593a commit 07dc367

File tree

4 files changed

+103
-27
lines changed

4 files changed

+103
-27
lines changed

src/concat-uint8array.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export function concatUint8Array(data: Uint8Array[]): Uint8Array {
2+
if (data.length === 0) {
3+
// no data received
4+
return new Uint8Array(0);
5+
}
6+
7+
let totalLength = 0;
8+
for (let i = 0; i < data.length; i++) {
9+
totalLength += data[i].length;
10+
}
11+
if (totalLength === 0) {
12+
// no data received
13+
return new Uint8Array(0);
14+
}
15+
16+
const result = new Uint8Array(totalLength);
17+
let offset = 0;
18+
for (let i = 0; i < data.length; i++) {
19+
result.set(data[i], offset);
20+
offset += data[i].length;
21+
}
22+
23+
return result;
24+
}

src/middleware/node/get-payload.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
1-
// remove type imports from http for Deno compatibility
2-
// see https://github.com/octokit/octokit.js/issues/2075#issuecomment-817361886
3-
// import type { IncomingMessage } from "node:http";
4-
// declare module "node:http" {
5-
// interface IncomingMessage {
6-
// body?: string;
7-
// }
8-
// }
1+
import { concatUint8Array } from "../../concat-uint8array.ts";
2+
93
type IncomingMessage = any;
104

11-
export function getPayload(request: IncomingMessage): Promise<string> {
5+
const textDecoder = new TextDecoder("utf-8", { fatal: false });
6+
const decode = textDecoder.decode.bind(textDecoder);
7+
8+
export async function getPayload(request: IncomingMessage): Promise<string> {
129
if (
1310
typeof request.body === "object" &&
1411
"rawBody" in request &&
15-
request.rawBody instanceof Buffer
12+
request.rawBody instanceof Uint8Array
1613
) {
17-
// The body is already an Object and rawBody is a Buffer (e.g. GCF)
18-
return Promise.resolve(request.rawBody.toString("utf8"));
14+
// The body is already an Object and rawBody is a Buffer/Uint8Array (e.g. GCF)
15+
return decode(request.rawBody);
1916
} else if (typeof request.body === "string") {
2017
// The body is a String (e.g. Lambda)
21-
return Promise.resolve(request.body);
18+
return request.body;
2219
}
2320

21+
// We need to load the payload from the request (normal case of Node.js server)
22+
const payload = await getPayloadFromRequestStream(request);
23+
return decode(payload);
24+
}
25+
26+
export function getPayloadFromRequestStream(
27+
request: IncomingMessage,
28+
): Promise<Uint8Array> {
2429
// We need to load the payload from the request (normal case of Node.js server)
2530
return new Promise((resolve, reject) => {
26-
let data: Buffer[] = [];
31+
let data: Uint8Array[] = [];
2732

2833
request.on("error", (error: Error) =>
2934
reject(new AggregateError([error], error.message)),
3035
);
31-
request.on("data", (chunk: Buffer) => data.push(chunk));
32-
request.on("end", () =>
33-
// setImmediate improves the throughput by reducing the pressure from
34-
// the event loop
35-
setImmediate(
36-
resolve,
37-
data.length === 1
38-
? data[0].toString("utf8")
39-
: Buffer.concat(data).toString("utf8"),
40-
),
41-
);
36+
request.on("data", data.push.bind(data));
37+
request.on("end", () => {
38+
const result = concatUint8Array(data);
39+
// Switch to queue microtask when we want to support bun and deno
40+
setImmediate(resolve, result);
41+
});
4242
});
4343
}

test/integration/get-payload.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe("getPayload", () => {
7070
const promise = getPayload(request);
7171

7272
// we emit data, to ensure that the body attribute is preferred
73-
request.emit("data", "bar");
73+
request.emit("data", Buffer.from("bar"));
7474
request.emit("end");
7575

7676
expect(await promise).toEqual("foo");
@@ -85,9 +85,20 @@ describe("getPayload", () => {
8585
const promise = getPayload(request);
8686

8787
// we emit data, to ensure that the body attribute is preferred
88-
request.emit("data", "bar");
88+
request.emit("data", Buffer.from("bar"));
8989
request.emit("end");
9090

9191
expect(await promise).toEqual("bar");
9292
});
93+
94+
it("should not throw an error if non-valid utf-8 payload was received", async () => {
95+
const request = new EventEmitter();
96+
97+
const promise = getPayload(request);
98+
99+
request.emit("data", new Uint8Array([226]));
100+
request.emit("end");
101+
102+
expect(await promise).toEqual("�");
103+
});
93104
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, it, assert } from "vitest";
2+
3+
import { concatUint8Array } from "../../src/concat-uint8array.ts";
4+
5+
describe("concatUint8Array", () => {
6+
it("returns an empty array when no data is provided", () => {
7+
const result = concatUint8Array([]);
8+
assert(result.length === 0);
9+
});
10+
11+
it("returns a single Uint8Array when one Uint8Array is provided", () => {
12+
const data = new Uint8Array([1, 2, 3]);
13+
const result = concatUint8Array([data]);
14+
assert(data !== result);
15+
assert(result.length === 3);
16+
assert(result[0] === 1);
17+
assert(result[1] === 2);
18+
assert(result[2] === 3);
19+
});
20+
21+
it("concatenates multiple Uint8Arrays into a single Uint8Array", () => {
22+
const data = [
23+
new Uint8Array([1, 2]),
24+
new Uint8Array([3, 4]),
25+
new Uint8Array([5, 6]),
26+
];
27+
const result = concatUint8Array(data);
28+
29+
assert(data[0] !== result);
30+
assert(data[1] !== result);
31+
assert(data[2] !== result);
32+
33+
assert(result.length === 6);
34+
assert(result[0] === 1);
35+
assert(result[1] === 2);
36+
assert(result[2] === 3);
37+
assert(result[3] === 4);
38+
assert(result[4] === 5);
39+
assert(result[5] === 6);
40+
});
41+
});

0 commit comments

Comments
 (0)