Theory (RFC 6455): Unless specified otherwise by an extension, frames have no semantic meaning. An intermediary might coalesce and/or split frames, [...] Practice: Some WebSocket servers reject large payload sizes. Other WebSocket servers reject CONTINUATION
| 225 | |
| 226 | |
| 227 | class Fragmentizer: |
| 228 | """ |
| 229 | Theory (RFC 6455): |
| 230 | Unless specified otherwise by an extension, frames have no semantic |
| 231 | meaning. An intermediary might coalesce and/or split frames, [...] |
| 232 | |
| 233 | Practice: |
| 234 | Some WebSocket servers reject large payload sizes. |
| 235 | Other WebSocket servers reject CONTINUATION frames. |
| 236 | |
| 237 | As a workaround, we either retain the original chunking or, if the payload has been modified, use ~4kB chunks. |
| 238 | If one deals with web servers that do not support CONTINUATION frames, addons need to monkeypatch FRAGMENT_SIZE |
| 239 | if they need to modify the message. |
| 240 | """ |
| 241 | |
| 242 | # A bit less than 4kb to accommodate for headers. |
| 243 | FRAGMENT_SIZE = 4000 |
| 244 | |
| 245 | def __init__(self, fragments: list[bytes], is_text: bool): |
| 246 | self.fragment_lengths = [len(x) for x in fragments] |
| 247 | self.is_text = is_text |
| 248 | |
| 249 | def msg(self, data: bytes, message_finished: bool): |
| 250 | if self.is_text: |
| 251 | data_str = data.decode(errors="replace") |
| 252 | return wsproto.events.TextMessage( |
| 253 | data_str, message_finished=message_finished |
| 254 | ) |
| 255 | else: |
| 256 | return wsproto.events.BytesMessage(data, message_finished=message_finished) |
| 257 | |
| 258 | def __call__(self, content: bytes) -> Iterator[wsproto.events.Message]: |
| 259 | if len(content) == sum(self.fragment_lengths): |
| 260 | # message has the same length, we can reuse the same sizes |
| 261 | offset = 0 |
| 262 | for fl in self.fragment_lengths[:-1]: |
| 263 | yield self.msg(content[offset : offset + fl], False) |
| 264 | offset += fl |
| 265 | yield self.msg(content[offset:], True) |
| 266 | else: |
| 267 | offset = 0 |
| 268 | total = len(content) - self.FRAGMENT_SIZE |
| 269 | while offset < total: |
| 270 | yield self.msg(content[offset : offset + self.FRAGMENT_SIZE], False) |
| 271 | offset += self.FRAGMENT_SIZE |
| 272 | yield self.msg(content[offset:], True) |
no outgoing calls
no test coverage detected
searching dependent graphs…