mirror of
https://github.com/THU-MAIC/OpenMAIC.git
synced 2026-10-17 05:29:46 +08:00
* fix(persistence): stop corrupting binary response bodies in the route adapter The embedded persistence route bridges a Node `RequestListener` to the Fetch API by hand. Its response object is cast with `as unknown as ServerResponse`, so the compiler checks none of that surface, and two parts of it were wrong in ways only a byte-carrying handler would hit. `end` decoded any `Uint8Array` chunk with `Buffer.from(chunk).toString()`, which is UTF-8. `ServerResponse.end` accepts a `Uint8Array`, and those bytes are not necessarily valid UTF-8, so every unpaired byte became U+FFFD -- silent corruption with no error anywhere. The adapter now buffers chunks as bytes and builds the response from them. `write` was absent entirely, making any chunked handler a runtime TypeError rather than a compile error. It is part of the surface this object claims to implement, so it is now implemented. Neither reaches a caller today: the document and runtime handlers only ever end with JSON strings, which took the string branch. Both are traps laid for the first handler that carries bytes. Both cases are pinned by tests that fail before this change -- the binary one on a byte comparison, the chunked one with a 500 from the TypeError. The existing adapter round-trip test, whose comment already called this the most bug-prone code in the route, covered only the string path. * fix(persistence): make the route adapter faithful where it carries bytes The embedded persistence route bridges a Node `RequestListener` to the Fetch API by hand, and its response object is cast `as unknown as ServerResponse`, so the compiler checks none of that surface. Five divergences are closed here; the first two are the ones that corrupt data, the last three were raised in review. **`end` decoded bytes as text.** It ran `Buffer.from(chunk).toString()` on a `Uint8Array` chunk, which is UTF-8, so every byte outside that encoding became U+FFFD -- silent corruption with no error anywhere. The adapter now buffers chunks as bytes. **`write` was absent**, making any chunked handler a runtime `TypeError` rather than a compile error. **`write` callbacks ran inline.** Node invokes them after the chunk is handed off. A handler that writes its next chunk from each callback therefore recursed synchronously and could overflow the stack, and a throwing callback threw in the wrong execution phase. They are now deferred. **The encoding argument was ignored.** `write(s, 'latin1')` emitted `0xc3 0xa9` where Node emits `0xe9`, corrupting the body and potentially invalidating a `Content-Length` the handler had set. Both `write` and `end` now parse their full overload set and pass the encoding through. Node's own behaviour for an invalid encoding was established by probing `ServerResponse` on Node 22 rather than assumed: it throws `ERR_UNKNOWN_ENCODING` synchronously, so the adapter validates with `Buffer.isEncoding` and lets `Buffer.from` raise the native error. **Bodyless statuses were not suppressed.** Node discards writes after `writeHead(204)` or `writeHead(304)`; this passed the buffered bytes to the Fetch `Response` constructor, which throws for those statuses, so the request became a 500 instead of the intended 204 or 304. Buffered data is now discarded for 204, 205 and 304, and for `HEAD`. None of the five reaches a caller today -- the document and runtime handlers only ever end with JSON strings. They are traps laid for the first handler that carries bytes, which is why they surfaced while wiring the asset server backend, whose byte routes hit the first two immediately. Each is pinned by a test that fails when its fix is reverted. --------- Co-authored-by: 杨慎 <117187635+cosarah@users.noreply.github.com>