Buffer the WebSocket handshake before writing it to the socket

Follow-up to #2514. The rebuilt handshake wrote the request line straight
to the socket, so a header rejected by check_and_write_headers left a
truncated "GET /ws HTTP/1.1" sitting in the peer's buffer before the
connection was torn down, and every header cost its own small write.

Build the request into a BufferStream and flush it in one go, matching
ClientImpl::write_request. The new test drives a raw listener and asserts
the peer sees a clean EOF with zero bytes; without this change it observes
18.

Also fold WebSocketTest.HostHeaderInHandshake into
WebSocketTest.DefaultHeadersInHandshake, which covers the same Host
assertion through the capture helper #2514 introduced.
This commit is contained in:
yhirose committed 2026-08-01 14:09:53 -04:00
1 parent f406808497
commit 447b9c4a29
2 files changed
+71 -43

No files matched your search

+11 -2
View File
@@ -9074,13 +9074,22 @@ inline bool perform_websocket_handshake(Stream &strm, Request &req,
req.headers.emplace("Sec-WebSocket-Key", client_key);
req.headers.emplace("Sec-WebSocket-Version", "13");
if (write_request_line(strm, req.method, req.path) < 0) { return false; }
// Build the request in memory first, like ClientImpl::write_request does.
// Writing straight to the socket would leak a request line onto the wire
// before check_and_write_headers gets a chance to reject an invalid header,
// and would emit one small write per header.
BufferStream bstrm;
if (write_request_line(bstrm, req.method, req.path) < 0) { return false; }
auto error = Error::Success;
if (!check_and_write_headers(strm, req.headers, write_headers, error)) {
if (!check_and_write_headers(bstrm, req.headers, write_headers, error)) {
return false;
}
const auto &data = bstrm.get_buffer();
if (!write_data(strm, data.data(), data.size())) { return false; }
// Verify 101 response and Sec-WebSocket-Accept header
auto expected_accept = websocket_accept_key(client_key);
return read_websocket_upgrade_response(strm, expected_accept,