Fix websocket body writes to frame as one multi-part message. - #844
Merged
Conversation
socket::async_write now threads a fin flag through to beast's async_write_some instead of always writing a whole message. do_body_write and do_body_notify pass fin = !out->more, so a response body served across multiple writer.get() chunks is sent as a single multi-frame websocket message (fin set only on the final chunk) instead of N independent messages. ws_write/tcp_write keep fin = true, since they already hand off a single, already-whole buffer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes websocket body writes so a single logical response is delivered as one multi-frame websocket message instead of being split into several independent messages.
Problem
json::body<>::writer(and any other body writer) may need multipleget()passes to serialize a payload larger than its buffer/size_hint.socket::do_body_write/do_body_notifyloop over these passes and callsocket::async_writeonce per chunk. Over TCP this is invisible (the stream has no message boundaries), but over websocket each call went through beast'sstream::async_write, which always writes a complete message withfinset.The result: an N-chunk serialization became N independent websocket messages instead of one message split across N frames — breaking any client that expects one WS message per logical response.
Fix
socket::async_write(socket.hpp/socket.cpp) now takes an explicitfinflag and, on the websocket path, calls beast'sasync_write_some(fin, buffer, handler)instead of the whole-messageasync_write.do_body_write/do_body_notify(socket_body.cpp) passfin = !out->more, so only the final chunk of a body closes the message.do_ws_write(socket_ws.cpp), andtcp_writewhich routes through it, keep passingfin = true— they already hand off a single, complete buffer in one call, so behavior there is unchanged.binary()/text()only take effect at the start of a message (stream::binarydocs), so calling it on every frame — as the code already did — remains safe with no changes needed there.Testing
test/messages/json_body_writer.cpp: forces a smallsize_hintrelative to the payload sowriter::get()must be called many times, and asserts themoresequence (true, ..., false) and that the reassembled chunks equal the full serialization.test/net/socket.cpp: a real loopback websocket connection where the server writes a multi-chunk JSON body and a raw Boost.Beast client (independent of the code under test) performs a single blockingread(). Beast'sread()only returns once a complete message (fin) is received, so the test only passes if all chunks were sent as one multi-frame message — exactly the scenario this fix addresses.session_inbound_testsfailures seen in one earlier run were reproduced as pre-existing flakes, not caused by this change.