Skip to content

Refactor the code handling WS data event to remove code duplication and fix the usages of pinfo->opcode in examples#388

Merged
mathieucarbou merged 4 commits intomainfrom
fix/385
Feb 13, 2026
Merged

Refactor the code handling WS data event to remove code duplication and fix the usages of pinfo->opcode in examples#388
mathieucarbou merged 4 commits intomainfrom
fix/385

Conversation

@mathieucarbou
Copy link
Member

@mathieucarbou mathieucarbou commented Feb 13, 2026

this is a little refactoring following PR #385.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the WebSocket data event handling code to eliminate duplication by extracting repeated logic into a dedicated helper function. The refactoring addresses code that was duplicated across two locations where WebSocket data frames are processed.

Changes:

  • Extracted duplicated buffer allocation and event handling logic into a new _handleDataEvent helper method
  • Modernized memory management by replacing manual malloc/free calls with std::unique_ptr
  • Consolidated error handling for allocation failures into the new helper function

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/AsyncWebSocket.h Added declaration for the new _handleDataEvent private helper method
src/AsyncWebSocket.cpp Replaced two instances of duplicated code with calls to _handleDataEvent and implemented the new helper method using modern C++ memory management

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mathieucarbou mathieucarbou changed the title Refactor the code handling WS data event to remove code duplication Refactor the code handling WS data event to remove code duplication and fix the usages of pinfo->opcode in examples Feb 13, 2026
@mathieucarbou mathieucarbou force-pushed the fix/385 branch 2 times, most recently from 5b0c586 to 8e63fe2 Compare February 13, 2026 09:41
…nd fix the usages of pinfo->opcode in examples
Copy link

@willmmiles willmmiles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good to me, though I haven't personally tested it. We could optimize further, and IMO an opt-out feature would be nice to have, but this is already a clean improvement.

Aside: frankly, seeing this make me wonder if there isn't some small value in doing a similar factor-out-to-function on some of the other handler paths, even though there would be only one call site for each. I find this actually makes the code easier to read, keeping the "what to do with a specific frame" in an easily identifiable section unmixed from the "how we find frames in the packet buffer" loop.

@mathieucarbou
Copy link
Member Author

Aside: frankly, seeing this make me wonder if there isn't some small value in doing a similar factor-out-to-function on some of the other handler paths, even though there would be only one call site for each. I find this actually makes the code easier to read, keeping the "what to do with a specific frame" in an easily identifiable section unmixed from the "how we find frames in the packet buffer" loop.

yes I agree!

@mathieucarbou
Copy link
Member Author

@willmmiles I added a commit based on our discussion here.

I tested sending a long message with websocat with openssl rand -hex 2000 | websocat ws://192.168.4.1/ws.

It turns out the copy will always be used as long as the tcp buffer is filled. But if the last frame is shorter, then the disconnect control frame can be put together by websocat or browser. So for the last frame processing, we can see that a backup is done, then last character restored, then the control frame for the disconnection is processed.

I also fixed another bug where the message_opcode was not initialized in the case of a short message where there is 1 segment 1 frame.

Comment on lines +647 to +657
if (_pinfo.index == 0) { // first fragment of the frame
// init message_opcode for this frame
// note: For next WS_CONTINUATION frames, they have opcode 0, so message_opcode will stay like the first frame
if (_pinfo.opcode == WS_TEXT || _pinfo.opcode == WS_BINARY) {
_pinfo.message_opcode = _pinfo.opcode;
}
// init frame number to 0 if only 1 frame or if this is the first frame of a fragmented message
if (_pinfo.final || datalen < _pinfo.len) {
_pinfo.num = 0;
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

message_opcode was not properly initialized when the text/binary frame fitted in 1 tcp buffer

=> (datalen + _pinfo.index) == _pinfo.len)

@willmmiles
Copy link

@willmmiles I added a commit based on our discussion here.

I tested sending a long message with websocat with openssl rand -hex 2000 | websocat ws://192.168.4.1/ws.

It turns out the copy will always be used as long as the tcp buffer is filled. But if the last frame is shorter, then the disconnect control frame can be put together by websocat or browser. So for the last frame processing, we can see that a backup is done, then last character restored, then the control frame for the disconnection is processed.

I also fixed another bug where the message_opcode was not initialized in the case of a short message where there is 1 segment 1 frame.

The way websocat interacts with the socket API causes the kernel to push a TCP packet at the end of every message -- so yes, it won't trigger the optimization very often. This isn't strictly speaking guaranteed, but in practice the acks come back fast enough that this is pretty much always the case. So as long as you're testing with unmodified websocat, you'll basically always see the end of a message be the end of a TCP packet.

I do not know if browsers behave the same way, but I suspect so. In practice I guess the implication is that the multi-packet optimization doesn't help very often.

I did hack websocat to set the TCP_PUSH socket option to force TCP frames to be filled completely before sending as a way of testing #383 - I can post up those sources later if you'd find them useful. It was helpful because it allowed me to arbitrarily align the header of the next message by changing the previous message length.

@mathieucarbou
Copy link
Member Author

I did hack websocat to set the TCP_PUSH socket option to force TCP frames to be filled completely before sending as a way of testing #383

Ahah that’s clever! I searched for the CLI parameters to tweak the tcp layer but I completely forgot it was OSS! I don’t know though of I would be able to do what you did 😅

@mathieucarbou mathieucarbou merged commit 3957c72 into main Feb 13, 2026
33 checks passed
@mathieucarbou mathieucarbou deleted the fix/385 branch February 13, 2026 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants