Conversation
A websocket client may send its first frame in the same TCP segment as the upgrade request. On the server, that frame is the unparsed tail of the recv buffer handed to HttpHandler::FeedRecvData: the MESSAGE_COMPLETE hook runs synchronously inside http-parser and emits the 101, but the http parser stops at the request boundary and returns nfeed < len, so on_recv saw 'http parse error: success' and closed the healthy upgraded io -- the channel died on the client's first message. Track the completed upgrade with a ws_upgraded flag and, when set, feed the remaining bytes of the same buffer to ws_parser instead of failing. Also seed last_recv_pong_time/last_send_ping_time at upgrade time: both start at 0, so the first heartbeat tick would see recv < send and close the channel before any ping/pong round-trip happened. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Thanks for the detailed report. On the heartbeat change: with both timestamps at 0, the first tick evaluates 0 < 0 as false and sends a ping. The channel is only closed if no pong has arrived by the second tick, so seeding the timestamps doesn't change behavior. |
|
Thanks for the detailed review — agreed on both points. You are right that per RFC 6455 §4.1 a conforming client must wait for the 101 before sending, and right that the heartbeat seeding was a no-op (first tick evaluates On the server-side tolerance itself: we will keep the Closing this PR; thanks again for taking the time to look at it. |
…osed upstream, RFC 6455 argues clients must wait for 101) Upstream (ithewei/libhv) rejected the server-side special case in PR ithewei#895: a conforming client MUST wait for the 101 before sending (RFC 6455 4.1), and the heartbeat timestamp seeding was a no-op (first tick: 0 < 0 is false -> sends ping). Revert the working tree to the 0db9c63 pin content; clients must send from OnOpen. Original delta kept on branch fix-ws-first-frame-after-upgrade for reference. Co-Authored-By: Claude Code <noreply@anthropic.com>
fix(http): parse ws frames wrapped in the handshake TCP segment
Problem
A WebSocket client may legally send its first frame in the same TCP segment as the
upgrade request (nothing in RFC 6455 forbids pipelining the first frame onto the
handshake). On the server this kills the channel deterministically:
HttpHandler::FeedRecvDatadispatches the buffer through theHTTP_V1branch →http_parser_executestops at the request boundary, so the frame bytes are theunparsed tail of the same buffer (
nfeed < len).HP_MESSAGE_COMPLETEhook runs synchronously inside that parse: the 101 issent and
SwitchWebSocket()flipsprotocoltoWEBSOCKET— but the http parserstill reports partial consumption for this call.
on_recv(HttpServer.cpp) seesnfeed != readbytes→ logshttp parse error: success→ closes the perfectly healthy upgraded io.The client observes an open→close loop on every reconnect (it sends immediately, so
it can never stay connected). Sending only after the 101 arrives (e.g. waiting for
onopen, or a fixed delay) hides the bug — which is why the existing examples neverhit it.
Secondary bug fixed in the same path
SwitchWebSocket()leftlast_recv_pong_time/last_send_ping_timeat theirconstructor value
0. The first heartbeat tick therefore always seeslast_recv_pong_time < last_send_ping_timeand closes the channel before anyping/pong round-trip has had a chance to happen, whenever the client has not
already sent a pong. Both timestamps are now seeded at upgrade time.
Fix
HttpHandlergains aws_upgradedbit, set bySwitchWebSocket().HTTP_V1recv branch, after feeding the http parser, checks for a completedupgrade + leftover bytes and feeds exactly that tail to
ws_parserinstead offailing. Real pipelined HTTP requests are unaffected: the tail path only triggers
once
protocol == WEBSOCKET.Repro
Server with
HttpServiceonopen/onmessage(or any of the bundled ws examples withthe client-side sleep removed); client that sends a text frame right after writing
the handshake (single TCP segment):
Verified with a real client/server pair (login + market-subscribe traffic) plus the
bundled http/ws test examples — no regressions.
🤖 Generated with Claude Code