Files
bun.sh/test/js/web/fetch/fetch-gzip.test.ts
Don Isaac 81ecf7556c ci: repair lint setup and run it in CI (#15720)
Co-authored-by: Don Isaac <don@bun.sh>
2025-01-08 07:12:18 +00:00

225 lines
6.4 KiB
TypeScript

import { Socket } from "bun";
import { beforeAll, expect, it } from "bun:test";
import { gcTick } from "harness";
import path from "path";
const gzipped = path.join(import.meta.dir, "fixture.html.gz");
const html = path.join(import.meta.dir, "fixture.html");
let htmlText: string;
beforeAll(async () => {
htmlText = (await Bun.file(html).text()).replace(/\r\n/g, "\n");
});
it("fetch() with a buffered gzip response works (one chunk)", async () => {
using server = Bun.serve({
port: 0,
async fetch(req) {
gcTick(true);
return new Response(require("fs").readFileSync(gzipped), {
headers: {
"Content-Encoding": "gzip",
"Content-Type": "text/html; charset=utf-8",
},
});
},
});
gcTick(true);
const res = await fetch(server.url, { verbose: true });
gcTick(true);
const arrayBuffer = await res.arrayBuffer();
const clone = new Buffer(arrayBuffer);
gcTick(true);
await (async function () {
const second = Buffer.from(htmlText);
gcTick(true);
expect(second.equals(clone)).toBe(true);
})();
gcTick(true);
});
it("fetch() with a redirect that returns a buffered gzip response works (one chunk)", async () => {
using server = Bun.serve({
port: 0,
async fetch(req) {
if (req.url.endsWith("/redirect"))
return new Response(await Bun.file(gzipped).arrayBuffer(), {
headers: {
"Content-Encoding": "gzip",
"Content-Type": "text/html; charset=utf-8",
},
});
return Response.redirect("/redirect");
},
});
const url = new URL("hey", server.url);
const res = await fetch(url, { verbose: true });
const text = (await res.text()).replace(/\r\n/g, "\n");
expect(text).toEqual(htmlText);
});
it("fetch() with a protocol-relative redirect that returns a buffered gzip response works (one chunk)", async () => {
using server = Bun.serve({
port: 0,
async fetch(req, server) {
if (req.url.endsWith("/redirect"))
return new Response(await Bun.file(gzipped).arrayBuffer(), {
headers: {
"Content-Encoding": "gzip",
"Content-Type": "text/html; charset=utf-8",
},
});
const { host } = server.url;
return Response.redirect(`://${host}/redirect`);
},
});
const res = await fetch(new URL("hey", server.url), { verbose: true });
expect(new URL(res.url)).toEqual(new URL("redirect", server.url));
expect(res.redirected).toBe(true);
expect(res.status).toBe(200);
const text = (await res.text()).replace(/\r\n/g, "\n");
expect(text).toEqual(htmlText);
});
it("fetch() with a gzip response works (one chunk, streamed, with a delay)", async () => {
using server = Bun.serve({
port: 0,
fetch(req) {
return new Response(
new ReadableStream({
type: "direct",
async pull(controller) {
await 2;
const buffer = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer();
controller.write(buffer);
controller.close();
},
}),
{
headers: {
"Content-Encoding": "gzip",
"Content-Type": "text/html; charset=utf-8",
"Content-Length": "1",
},
},
);
},
});
const res = await fetch(server.url);
const text = (await res.text()).replace(/\r\n/g, "\n");
expect(text).toEqual(htmlText);
});
it("fetch() with a gzip response works (multiple chunks, TCP server)", async done => {
const compressed = await Bun.file(gzipped).arrayBuffer();
var socketToClose!: Socket;
let pending,
pendingChunks = [];
const server = Bun.listen({
hostname: "localhost",
port: 0,
socket: {
drain(socket) {
if (pending) {
while (pendingChunks.length) {
const chunk = pendingChunks.shift();
const written = socket.write(chunk);
if (written < chunk.length) {
pendingChunks.push(chunk.slice(written));
return;
}
}
const resolv = pending;
pending = null;
resolv();
}
},
async open(socket) {
socketToClose = socket;
var corked: any[] = [];
var cork = true;
let written = 0;
let pendingChunks = [];
async function write(chunk: any) {
let defer = Promise.withResolvers();
if (cork) {
corked.push(chunk);
}
if (!cork && corked.length) {
const toWrite = corked.join("");
const wrote = socket.write(toWrite);
if (wrote !== toWrite.length) {
pendingChunks.push(toWrite.slice(wrote));
}
corked.length = 0;
}
if (!cork) {
if (pendingChunks.length) {
pendingChunks.push(chunk);
pending = defer.resolve;
await defer.promise;
defer = Promise.withResolvers();
pending = defer.resolve;
}
const written = socket.write(chunk);
if (written < chunk.length) {
console.log("written", written);
pendingChunks.push(chunk.slice(written));
pending = defer.resolve;
await defer.promise;
defer = Promise.withResolvers();
pending = defer.resolve;
}
}
const promise = defer.promise;
if (pendingChunks.length) {
pending = promise;
await promise;
} else {
pending = null;
}
}
await write("HTTP/1.1 200 OK\r\n");
await write("Content-Encoding: gzip\r\n");
await write("Content-Type: text/html; charset=utf-8\r\n");
await write("Content-Length: " + compressed.byteLength + "\r\n");
await write("X-WTF: " + "lol".repeat(1000) + "\r\n");
await write("\r\n");
for (var i = 100; i < compressed.byteLength; i += 100) {
cork = false;
await write(compressed.slice(i - 100, i));
}
await write(compressed.slice(i - 100));
await write("\r\n");
socket.flush();
}
},
});
await 1;
const res = await fetch(`http://${server.hostname}:${server.port}`);
const text = (await res.text()).replace(/\r\n/g, "\n");
expect(text).toEqual(htmlText);
socketToClose.end();
server.stop();
done();
});