Skip to content

perf: trim body bytes before UTF-8 decoding - #109

Merged
AmisKwok merged 1 commit into
mainfrom
perf/trim-body-decode
Sep 24, 2026
Merged

AmisKwok merged 1 commit into
mainfrom
perf/trim-body-decode

Conversation

@AmisKwok

@AmisKwok AmisKwok commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary / 摘要

Trim request/response body bytes to the panel's preview cap before UTF-8 decoding, so a 512 KB body is no longer decoded in full just to keep the first 32K characters. / 在 UTF-8 解码前按面板预览上限裁剪请求 / 响应体字节,不再为保留前 32K 字符而完整解码 512 KB 的 body。

Changes / 变更

  • Add _bytesForPreviewDecode in http_interceptor.dart (shared by both part files): clips the byte prefix to maxChars * 4 + 3 — one UTF-8 char is at most 4 bytes, plus 3 so a multi-byte char is never cut in half. / 在 http_interceptor.dart 新增 _bytesForPreviewDecode(两个 part 共用):把字节前缀裁剪到 maxChars * 4 + 3 —— UTF-8 单字符最多 4 字节,额外 3 字节确保不会把多字节字符切在中间。
  • Response side: _decodeBodyOrHexPreview takes maxChars and decodes only the trimmed prefix; the base64 / hex binary fallback still reads the original bytes, so binary responses behave exactly as before. / 响应侧:_decodeBodyOrHexPreview 新增 maxChars,只解码裁剪后的前缀;base64 / hex 的二进制兜底仍基于原始字节,二进制响应行为完全不变。
  • Request side (inspector_http_client.dart): same trimming before utf8.decode for captured request bodies. / 请求侧(inspector_http_client.dart):请求体解码前同样剪枝。
  • Add the InspectorService.maxBodyPreviewBytes getter so interceptors can read the configured preview cap (it used to be private and invisible to them). / 新增 InspectorService.maxBodyPreviewBytes getter,让拦截器能读取配置的预览上限(原来该上限私有、拦截器无法感知)。
  • Bump version to 1.13.0 across pubspec.yaml, ios/zero_inspector_kit.podspec, InspectorVersion.value, both READMEs (install snippets + upgrade callout) and CHANGELOG.md; regenerate docs/ via sync-docs.mjs. / 版本升至 1.13.0,同步 pubspec.yaml、ios/zero_inspector_kit.podspec、InspectorVersion.value、两个 README(安装片段与升级提示)与 CHANGELOG.md;并通过 sync-docs.mjs 重新生成 docs/。

Context / 背景

The interceptor buffers up to 512 KB of a body, but NetworkRequest.copyWith(maxBodyBytes:) truncates the preview to 32K characters — so the decode paid for a tail that is discarded immediately (up to ~16x wasted work for ASCII, ~4x for CJK). That decode is synchronous on the UI isolate, and it was the only realistic way the inspector could cost the host app a frame on large responses. / 拦截器最多缓冲 512 KB body,而 NetworkRequest.copyWith(maxBodyBytes:) 会把预览截断到 32K 字符 —— 解码成本全花在随即被丢弃的尾部(纯 ASCII 最多浪费约 16 倍,中文约 4 倍)。该解码同步发生在 UI isolate 上,是检查器在超大响应时唯一可能让宿主 App 掉一帧的地方。

An extra isolate was considered and rejected: Flutter isolates share no memory, so the cross-isolate copy costs as much as (or more than) the decode it saves, and the capture probes — HttpOverrides, debugPrint override, addTimingsCallback, NavigatorObserver — must live in the main isolate anyway. Preview content and length are unchanged by this PR. / 曾评估过另开 isolate,已否决:Flutter isolate 之间不共享内存,跨 isolate 拷贝的成本不低于省下的解码成本,而且采集探针(HttpOverrides、debugPrint 覆写、addTimingsCallback、NavigatorObserver)本身必须在主 isolate。本次改动不改变预览内容与长度。

Checklist / 检查项

  • Title follows Conventional Commits / 标题符合约定式提交
  • CI checks pass after merge / 合入后 CI 通过

Test plan

  • dart format . — 113 files, 0 changed
  • flutter analyze — No issues found
  • flutter test — 285 passed (incl. version_consistency_test)
  • dart run tool/check_release.dart — all version references consistent with 1.13.0
  • Manual: fire a response larger than 512 KB and confirm the panel still shows exactly the first 32K characters plus the [… truncated N chars …] marker / 手工:发起一个大于 512 KB 的响应,确认面板仍显示前 32K 字符与 [… truncated N chars …] 提示
  • Manual: fire a binary (image / gzip) response and confirm it still renders via the base64 or hex fallback instead of being misread as text / 手工:发起二进制(图片 / gzip)响应,确认仍走 base64 或 hex 兜底,不会被误判成文本

🤖 Generated with Zero Buddy

Bodies were decoded in full (up to 512 KB) only to keep the first 32K characters of the panel preview. Clip the byte prefix to maxChars * 4 + 3 before utf8.decode so the tail that truncation discards is never decoded (~1/16 work for ASCII, ~1/4 for CJK); preview content is unchanged and the base64/hex binary fallback still reads the original bytes.

Bump version to 1.13.0 (pubspec / podspec / InspectorVersion / both READMEs / CHANGELOG) and regenerate docs/.
@AmisKwok
AmisKwok merged commit 9287407 into main Sep 24, 2026
9 checks passed
@AmisKwok
AmisKwok deleted the perf/trim-body-decode branch September 24, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant