Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions src/jsc/bindings/node/JSNodeHTTPServerSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,20 @@ extern "C" void Bun__NodeHTTP__onReadParsed(int ssl, us_socket_t* socket)
}
}

// Parsing stops first: a TLS close waits for unsent ciphertext, and uWS answers a request that it reads meanwhile.
extern "C" void Bun__NodeHTTP__refuseConnection(int ssl, us_socket_t* socket)
{
if (us_socket_is_closed(socket)) {
return;
}
auto* serverSocket = ssl ? getNodeHTTPServerSocket<true>(socket) : getNodeHTTPServerSocket<false>(socket);
if (!serverSocket) {
return;
}
serverSocket->stopHTTPParsing();
serverSocket->close();
Comment thread
robobun marked this conversation as resolved.
}

// Returns the JSNodeHTTPServerSocket already attached to this raw socket, or
// creates (and attaches) one. Used for connections that have not produced a
// parsed request yet: the 'clientError' path and the connection-accept path.
Expand Down
43 changes: 34 additions & 9 deletions src/runtime/server/server_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3288,15 +3288,18 @@ where
/// its handshake completed). Hands the JSNodeHTTPServerSocket to the JS
/// `onConnection` callback so `node:http` can emit 'connection' before any
/// request bytes arrive.
pub(crate) fn on_connection_callback(&self, socket: *mut c_void) -> JsResult<()> {
if self.js_value_for_dispatch().is_none() {
return Ok(());
}
let callback = self.on_connection;
pub(crate) fn on_connection_callback(this: *const Self, socket: *mut c_void) -> JsResult<()> {
let (callback, global) = {
// SAFETY: uWS runs the filter of a live server. Its listeners can free it, so this borrow ends before them.
let this = unsafe { &*this };
if this.js_value_for_dispatch().is_none() {
return Ok(());
}
(this.on_connection, this.global())
};
if callback.is_empty() {
return Ok(());
}
let global = self.global();
let node_socket = jsc::from_js_host_call(&global, || {
Bun__getOrCreateNodeHTTPServerSocket(SSL, socket, &global)
})?;
Expand All @@ -3310,6 +3313,24 @@ where
Ok(())
}

/// Node.js exits at a listener's error that nothing handles, and Bun goes on to read the request: https://github.com/nodejs/node/blob/v26.3.0/src/node_errors.cc#L1369-L1375
#[inline(never)] // The filter thunk returns before this frame for 3 of its 4 calls per connection.
pub(crate) fn run_connection_listeners(this: *const Self, socket: *mut c_void) {
// SAFETY: uWS runs the filter of a live server. Its listeners can free it: `bun test` runs the next test from inside the report of their error.
let vm = unsafe { (*this).vm };
let unhandled_errors = vm.unhandled_error_counter;
crate::dispatch::fold(Self::on_connection_callback(this, socket));
if vm.unhandled_error_counter != unhandled_errors {
bun_core::hint::cold();
// `bun test` takes every error of its main VM, as a handler does.
let runner_took_it = vm.worker.is_none()
&& jsc::virtual_machine::isBunTest.load(core::sync::atomic::Ordering::Relaxed);
if !runner_took_it {
Bun__NodeHTTP__refuseConnection(c_int::from(SSL), socket);
}
}
}

// `js_gc_route_list_set` / `ptr_to_js` live on the unbounded
// `impl NewServer` in mod.rs; do not redefine them here.
}
Expand Down Expand Up @@ -3457,9 +3478,10 @@ fn server_set_on_connection(
}
// SAFETY: user_data is the `*mut Self` registered below;
// socket is a live uWS socket for this server's group.
// Shared — the callback re-enters JS.
let this = unsafe { &*user_data.cast::<$T>() };
crate::dispatch::fold(this.on_connection_callback(socket.cast::<c_void>()));
<$T>::run_connection_listeners(
user_data.cast::<$T>(),
socket.cast::<c_void>(),
);
}
if first {
// S008: `NewApp<SSL>` is a ZST opaque — safe `*mut → &mut` deref.
Expand Down Expand Up @@ -3684,6 +3706,9 @@ unsafe extern "C" {
global: &JSGlobalObject,
) -> JSValue;

// `socket` is the same live `uws::Socket*`: stops its HTTP parsing and closes it.
safe fn Bun__NodeHTTP__refuseConnection(ssl: c_int, socket: *mut c_void);

// `&JSGlobalObject` / `&mut JSValue` discharge the deref'd-param
// preconditions; `request_ptr`/`req` are opaque handles that C++ stores or
// forwards (module-private — sole callers pass live pointers).
Expand Down
182 changes: 182 additions & 0 deletions test/js/node/http/node-http-connection-listener-throw-fixture.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading