Run the pre-routing handler once for an unmatched WebSocket upgrade

An upgrade request goes through the pre-routing handler before the
WebSocket routes are matched. When none matched, it was routed as a
plain HTTP request, and routing() ran the handler a second time. Skip
it there for an upgrade request.
This commit is contained in:
yhirose committed 2026-10-18 12:41:23 -04:00
1 parent cc9d67404e
commit 1d5f0323b1
2 files changed
+90 -7

No files matched your search

+79
View File
@@ -27435,6 +27435,85 @@ TEST(WebSocketPreRoutingTest, RejectWithoutAuth) {
t.join();
}
TEST(WebSocketPreRoutingTest, RunsOnceForUnmatchedUpgrade) {
Server svr;
std::atomic<int> pre_routing_calls{0};
std::atomic<int> pre_request_calls{0};
svr.set_pre_routing_handler([&](const Request &req, Response &res) {
pre_routing_calls++;
if (req.path == "/handled") {
res.set_content("handled", "text/plain");
return Server::HandlerResponse::Handled;
}
return Server::HandlerResponse::Unhandled;
});
svr.set_pre_request_handler([&](const Request &, Response &) {
pre_request_calls++;
return Server::HandlerResponse::Unhandled;
});
svr.Get("/http", [](const Request &, Response &res) {
res.set_content("http", "text/plain");
});
svr.WebSocket("/ws",
[](const Request &, ws::WebSocket &ws) { ws.send("ws"); });
auto port = svr.bind_to_any_port("localhost");
std::thread t([&]() { svr.listen_after_bind(); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
});
svr.wait_until_ready();
Headers upgrade = {{"Upgrade", "websocket"},
{"Connection", "Upgrade"},
{"Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="},
{"Sec-WebSocket-Version", "13"}};
auto get = [&](const char *path) {
pre_routing_calls = 0;
return Client("localhost", port).Get(path, upgrade);
};
// No WebSocket route, but an HTTP one
auto res = get("/http");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("http", res->body);
EXPECT_EQ(1, pre_routing_calls);
EXPECT_EQ(1, pre_request_calls);
// No route at all
res = get("/none");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::NotFound_404, res->status);
EXPECT_EQ(1, pre_routing_calls);
// Answered by the pre-routing handler
res = get("/handled");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("handled", res->body);
EXPECT_EQ(1, pre_routing_calls);
// A WebSocket route
pre_routing_calls = 0;
pre_request_calls = 0;
{
ws::WebSocketClient client("ws://localhost:" + std::to_string(port) +
"/ws");
ASSERT_TRUE(client.connect());
std::string msg;
ASSERT_TRUE(client.read(msg));
EXPECT_EQ("ws", msg);
client.close();
}
EXPECT_EQ(1, pre_routing_calls);
EXPECT_EQ(1, pre_request_calls);
}
TEST(WebSocketPreRequestTest, RejectWithoutAuth) {
Server svr;