Skip to content
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
70dd062
fix node:http proxy support: createConnection, upgrade sockets, socke…
robobun Mar 21, 2026
d400ae9
fix lint: prefix unused shouldKeepAlive parameter with underscore
robobun Mar 21, 2026
fa2cb42
address review: join duplicate headers, use setImmediate in test, fix…
robobun Mar 21, 2026
a10f89c
fix upgrade close handler spurious error, capture leftover bytes as h…
robobun Mar 21, 2026
5fa3a43
format: run prettier on _http_client.ts
robobun Mar 21, 2026
d1fc905
fix parser double-free, use IncomingMessage for upgrade response, rem…
robobun Mar 21, 2026
fda3158
format: alphabetize imports
robobun Mar 21, 2026
34676b8
fix chunked TE body framing in createConnection path
robobun Mar 21, 2026
21b707b
fix use-after-free: return after close() in non-chunked tryEnd path
robobun Mar 21, 2026
e5b0fa3
fix empty chunked body terminator and double error emission on connec…
robobun Mar 21, 2026
cecb8df
fix parser error double-emit, post-upgrade error guard, upgrade req/r…
robobun Mar 21, 2026
35f960c
check parser.finish() errors, clear timeout on upgrade, use describe.…
robobun Mar 21, 2026
1c45787
fix premature close after response, prevent double close on abort
robobun Mar 21, 2026
5a80b9f
fix zero-length Buffer chunked double-terminator
robobun Mar 21, 2026
c115786
fix request-smuggling regression: shutdown-only on post-uncork close,…
robobun Mar 22, 2026
ddfeac9
trigger CI rebuild
robobun Mar 22, 2026
01b5e18
drop async chunked close fix (conflicts with request-smuggling tests)…
robobun Mar 22, 2026
b493dd0
fix upgrade: call markAsUpgraded after emit, skip bidirectional test …
robobun Mar 22, 2026
3816143
add markAsUpgraded to CONNECT handler for Zig-level guards
robobun Mar 22, 2026
19031e1
revert markAsUpgraded from CONNECT handler (causes CI failures on Linux)
robobun Mar 22, 2026
cc091a3
retrigger CI
robobun Mar 22, 2026
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
33 changes: 33 additions & 0 deletions packages/bun-uws/src/HttpResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,19 @@ struct HttpResponse : public AsyncSocket<SSL> {
}
} else {
this->uncork();
/* After uncorking, check if we should close this connection.
* This handles the case where writeHead corked the socket outside
* the uWS request handler (async response), so the close check
* in HttpContext's onData handler never runs for this response. */
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) {
((AsyncSocket<SSL> *) this)->shutdown();
((AsyncSocket<SSL> *) this)->close();
return true;
}
}
}
}

/* tryEnd can never fail when in chunked mode, since we do not have tryWrite (yet), only write */
Expand Down Expand Up @@ -219,6 +232,17 @@ struct HttpResponse : public AsyncSocket<SSL> {
}
} else {
this->uncork();
/* After uncorking, check if we should close this connection.
* Same fix as the chunked path above. */
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) {
((AsyncSocket<SSL> *) this)->shutdown();
((AsyncSocket<SSL> *) this)->close();
return true;
}
}
}
}
}

Expand Down Expand Up @@ -764,6 +788,15 @@ struct HttpResponse : public AsyncSocket<SSL> {
return httpResponseData->isConnectRequest;
}

/* Mark this response as a connect-like request so that subsequent data
* is forwarded as raw bytes instead of going through the HTTP parser.
* Used for HTTP upgrade (e.g. WebSocket) when the application takes
* ownership of the socket via the node:http upgrade event. */
void markAsConnectRequest() {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
httpResponseData->isConnectRequest = true;
}

void setWriteOffset(uint64_t offset) {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();

Expand Down
4 changes: 4 additions & 0 deletions src/bun.js/api/server.classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ export default [
fn: "doResume",
length: 0,
},
markAsUpgraded: {
fn: "markAsUpgraded",
length: 0,
},
bufferedAmount: {
getter: "getBufferedAmount",
},
Expand Down
11 changes: 11 additions & 0 deletions src/bun.js/api/server/NodeHTTPResponse.zig
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,17 @@ pub fn doResume(this: *NodeHTTPResponse, globalObject: *jsc.JSGlobalObject, _: *
return result;
}

pub fn markAsUpgraded(this: *NodeHTTPResponse, _: *jsc.JSGlobalObject, _: *jsc.CallFrame) jsc.JSValue {
log("markAsUpgraded", .{});
if (this.raw_response) |resp| {
// Mark the uWS response as a connect request so that subsequent data
// is forwarded as raw bytes instead of going through the HTTP parser.
// This enables bidirectional streaming after an HTTP upgrade.
resp.markAsConnectRequest();
}
return .js_undefined;
}
Comment thread
robobun marked this conversation as resolved.

pub fn onRequestComplete(this: *NodeHTTPResponse) void {
if (this.flags.request_has_completed) {
return;
Expand Down
11 changes: 11 additions & 0 deletions src/deps/libuwsockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1842,6 +1842,17 @@ __attribute__((callback (corker, ctx)))
return uwsRes->isConnectRequest();
}
}
void uws_res_mark_as_connect_request(int ssl, uws_res_r res)
{
if (ssl) {
uWS::HttpResponse<true> *uwsRes = (uWS::HttpResponse<true> *)res;
uwsRes->markAsConnectRequest();
} else {
uWS::HttpResponse<false> *uwsRes = (uWS::HttpResponse<false> *)res;
uwsRes->markAsConnectRequest();
}
}

void *uws_res_get_native_handle(int ssl, uws_res_r res)
{
if (ssl)
Expand Down
11 changes: 11 additions & 0 deletions src/deps/uws/Response.zig
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ pub fn NewResponse(ssl_flag: i32) type {
return c.uws_res_is_connect_request(ssl_flag, res.downcast());
}

pub fn markAsConnectRequest(res: *Response) void {
c.uws_res_mark_as_connect_request(ssl_flag, res.downcast());
}

pub fn flushHeaders(res: *Response, flushImmediately: bool) void {
c.uws_res_flush_headers(ssl_flag, res.downcast(), flushImmediately);
}
Expand Down Expand Up @@ -590,6 +594,12 @@ pub const AnyResponse = union(enum) {
};
}

pub fn markAsConnectRequest(this: AnyResponse) void {
switch (this) {
inline else => |resp| resp.markAsConnectRequest(),
}
}

pub fn endStream(this: AnyResponse, close_connection: bool) void {
switch (this) {
inline else => |resp| resp.endStream(close_connection),
Expand Down Expand Up @@ -672,6 +682,7 @@ const c = struct {
pub extern fn us_socket_mark_needs_more_not_ssl(socket: ?*c.uws_res) void;
pub extern fn uws_res_state(ssl: c_int, res: *const c.uws_res) State;
pub extern fn uws_res_is_connect_request(ssl: i32, res: *c.uws_res) bool;
pub extern fn uws_res_mark_as_connect_request(ssl: i32, res: *c.uws_res) void;
pub extern fn uws_res_get_remote_address_info(res: *c.uws_res, dest: *[*]const u8, port: *i32, is_ipv6: *bool) usize;
pub extern fn uws_res_uncork(ssl: i32, res: *c.uws_res) void;
pub extern fn uws_res_end(ssl: i32, res: *c.uws_res, data: [*c]const u8, length: usize, close_connection: bool) void;
Expand Down
Loading
Loading