Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/http/ProxyTunnel.zig
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ fn onHandshake(this: *HTTPClient, handshake_success: bool, ssl_error: uws.us_bun

pub fn write(this: *HTTPClient, encoded_data: []const u8) void {
if (this.proxy_tunnel) |proxy| {
// Preserve TLS record ordering: if any encrypted bytes are buffered,
// enqueue new bytes and flush them in FIFO via onWritable.
if (proxy.write_buffer.isNotEmpty()) {
bun.handleOom(proxy.write_buffer.write(encoded_data));
return;
}
const written = switch (proxy.socket) {
.ssl => |socket| socket.write(encoded_data),
.tcp => |socket| socket.write(encoded_data),
Expand Down
40 changes: 40 additions & 0 deletions test/js/bun/http/proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,43 @@ test("axios with https-proxy-agent", async () => {
// did we got proxied?
expect(httpProxyServer.log).toEqual([`CONNECT localhost:${httpsServer.port}`]);
});

test("HTTPS over HTTP proxy preserves TLS record order with large bodies", async () => {
// Create a custom HTTPS server that returns body size for this test
using customServer = Bun.serve({
port: 0,
tls: tlsCert,
async fetch(req) {
// return the body size
const buf = await req.arrayBuffer();
return new Response(String(buf.byteLength), { status: 200 });
},
});

// Test with multiple body sizes to ensure TLS record ordering is preserved
// also testing several times because it's flaky otherwise
const testCases = [
16 * 1024 * 1024, // 16MB
32 * 1024 * 1024, // 32MB
];

for (const size of testCases) {
const body = new Uint8Array(size).fill(0x61); // 'a'

const response = await fetch(customServer.url, {
method: "POST",
proxy: httpProxyServer.url,
headers: { "Content-Type": "application/octet-stream" },
body,
keepalive: false,
tls: { ca: tlsCert.cert, rejectUnauthorized: false },
});

expect(response.ok).toBe(true);
expect(response.status).toBe(200);
const result = await response.text();

// recvd body size should exactly match the sent body size
expect(result).toBe(String(size));
}
});