
1. 什么是 Charles 重写Rewrite它到底能解决什么实际问题Charles 重写Rewrite功能不是简单的字符串替换而是一套在 HTTP/HTTPS 请求与响应流转过程中对原始数据进行结构化、条件化、可编程式干预的核心机制。它工作在代理服务器的中间层——当你的浏览器或 App 发出请求经过 Charles 代理时Rewrite 规则会在请求被转发给真实服务器前Request Phase或在服务器返回响应后、送达客户端前Response Phase精准地切入并修改指定字段。这听起来抽象但落到日常开发和测试中它的价值极其具体比如你正在调试一个前端页面后端接口返回的用户 ID 是123456但你临时想验证 UI 在 ID 为999999时的显示逻辑传统做法要么改后端代码、要么本地 mock 接口、要么等后端配合发测试数据——而用 Rewrite你只需配置一条规则把所有/api/user/profile响应体里的id: 123456替换成id: 999999刷新页面效果立现且不影响任何其他请求。再比如你发现某个 App 在 iOS 上因 User-Agent 字段缺失导致 CDN 返回了错误的图片格式而 Android 端正常这时你不需要反编译 App 或联系客户端团队直接用 Rewrite 在 Request Header 中强制注入User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148问题当场复现并验证修复方案。我做过上百个线上问题排查其中近四成根本不用动一行业务代码靠 Rewrite 就完成了“现场手术”。它本质上是一种零侵入、高时效、可逆可控的流量外科手术刀特别适合前端联调、灰度验证、兼容性测试、安全渗透如注入特定 header 触发 WAF 规则、甚至临时绕过某些前端校验逻辑做深度功能测试。注意它和 Map Local/Map Remote 的核心区别在于Map 是“换掉整个请求或响应”Rewrite 是“只动其中某一部分”粒度更细、干扰更小、复用性更强。如果你还在用全局替换或手动修改 JSON 再粘贴回响应窗口那说明你还没真正用上 Rewrite 的威力。2. Rewrite 功能的底层设计逻辑与关键能力边界2.1 Rewrite 的执行时机与作用域为什么它比 Fiddler 的 AutoResponder 更可靠Charles 的 Rewrite 功能严格遵循 HTTP 协议栈的处理流程其执行点被精确锚定在两个不可绕过的环节Request Pre-Forwarding和Response Post-Received。这意味着无论你的请求是通过 Chrome 的 DevTools Network 面板发起还是来自一个封装了 OkHttp 的 Android App抑或是 Electron 桌面应用只要流量经过 Charles 代理Rewrite 规则就必然生效——它不依赖于客户端是否支持某种特定的调试协议也不受 CORS 或同源策略的限制因为修改发生在代理层而非浏览器渲染引擎内部。相比之下Fiddler 的 AutoResponder 本质是“匹配 URL 后返回预设文件”它无法动态解析 JSON 响应体并修改其中某个嵌套字段而浏览器插件类工具如 ModHeader只能修改 Request Header对 Response Body 完全无能为力。Rewrite 的强大之处在于它提供了三重结构化操作能力Header 级别操作增、删、改任意 header、Body 级别操作支持正则、XPath、JSONPath 三种解析器、URL 级别操作重写 path、query 参数。更重要的是它支持条件触发你可以设定“仅当 Host 包含 api.example.com 且 Content-Type 为 application/json 时才启用此条规则”这避免了全局误伤。我曾在一个微服务架构项目中为不同环境dev/staging/prod配置了三套独立的 Rewrite 规则组并通过 Charles 的“Enable/Disable Group”功能一键切换彻底告别了每次换环境都要手动改代码里 API 地址的低效操作。这种基于流量特征的精准干预能力正是 Rewrite 区别于其他简单替换工具的根本所在。2.2 Rewrite 与 Map、Breakpoint 的协同关系它们不是替代而是分工很多新手会困惑Rewrite、Map Local、Breakpoint 这三个功能看起来都是“改流量”到底该用哪个答案是它们服务于完全不同的场景且经常组合使用。Map Local的本质是“劫持请求返回本地文件”适用于需要完整替换整个响应内容的场景比如用本地 HTML 替换线上页面做 UI 快速迭代或用 mock JSON 文件模拟一个尚未开发完成的接口。Breakpoint则是“暂停流量人工介入”它像一个交通警察在请求发出前或响应返回后强制拦停让你有机会手动编辑每一个字节适合做一次性、探索性的深度调试比如你想看看某个加密参数在发送前到底长什么样。而Rewrite是“自动化流水线”一旦配置完成它就在后台静默、稳定、高速地运行无需人工干预。三者真正的威力在于组合例如你先用 Breakpoint 拦截一个登录请求手动修改密码字段并放行观察后端返回的 token接着你把这个 token 提取出来用 Rewrite 规则自动注入到后续所有请求的 Authorization Header 中最后为了验证 token 过期逻辑你再用 Map Local 把/api/auth/refresh接口映射到一个返回 401 的本地 JSON 文件。这样一套组合拳下来整个认证链路的测试就变得无比高效。我见过太多团队把所有事情都堆在 Breakpoint 上结果调试过程像在玩解谜游戏耗时又易错而合理划分职责后90% 的常规修改交给 Rewrite 自动化5% 的深度探查交给 Breakpoint5% 的整页替换交给 Map效率提升是数量级的。2.3 Rewrite 的安全边界与性能影响它不会拖慢你的网络但可能拖慢你的判断Rewrite 功能本身对网络性能的影响微乎其微。Charles 的底层是 Java NIO 实现的高性能代理单条 Rewrite 规则的匹配与执行耗时通常在微秒级即使同时启用几十条规则实测对千兆带宽下的吞吐量影响也小于 0.5%。真正需要警惕的是它的逻辑边界模糊性。Rewrite 修改的是原始流量但它并不理解业务语义。举个真实案例某电商 App 的下单接口返回了一个order_id字段前端用它跳转到支付页。测试同学用 Rewrite 把order_id改成了一个固定值TEST_123结果支付网关校验失败报错Invalid order_id format。问题出在哪他没注意到order_id是由后端用时间戳随机数生成的其格式包含校验位而简单字符串替换破坏了这个校验逻辑。这提醒我们Rewrite 是一把锋利的刀但刀刃朝向哪里取决于你对业务的理解深度。另一个常见陷阱是循环引用比如你配置了一条规则将所有Content-Typeheader 改为application/json但恰好某个接口的响应头里包含了Content-Type: text/html而这个 HTML 页面里又内嵌了 JavaScriptJavaScript 又发起了新的请求……如果规则没有设置好 scope作用域就可能引发连锁反应。因此Charles 明确要求每条 Rewrite 规则必须指定Location作用位置Request Header / Response Header / Request Body / Response Body和Type操作类型Set / Append / Delete / Replace这不仅是技术约束更是思维约束——它强迫你明确回答“我要改什么在哪里改改成什么样” 这种强制结构化恰恰是避免误操作的最有效护栏。3. Rewrite 核心配置详解从零开始搭建一条可落地的规则3.1 创建 Rewrite 规则组命名规范决定后期维护成本在 Charles 中Rewrite 功能以“规则组Ruleset”为单位组织。不要直接在默认的 “Untitled Ruleset” 下添加规则这是新手最容易犯的错误。正确的做法是点击菜单栏Tools → Rewrite…在弹出窗口左下角点击Add创建一个新规则组并立即为其命名。命名不是随便写它应该体现环境 功能 责任人例如PROD-API-Auth-Token-Injection-john或STAGING-UI-Feature-Flag-Override-amy。为什么这么重要因为当项目上线后你可能要同时维护十几套规则如果都叫 “Test Rule 1”三个月后你自己都分不清哪条是干啥的。我吃过亏曾经一个支付模块的 Rewrite 规则被误停导致灰度发布失败排查了两小时才发现是名字太模糊和另一条“通用日志开关”规则混淆了。创建好规则组后右侧会出现 “Rules” 列表点击Add添加第一条规则。此时你会看到四个关键配置区Location作用位置、Match匹配条件、Operation操作类型、Value目标值。这四个字段就是 Rewrite 的全部骨架接下来我们逐个拆解。3.2 Location 与 Match精准定位是 Rewrite 成功的前提Location下拉菜单有四个选项它们决定了 Rewrite 的“手术刀”切在哪个位置Request Header修改发给服务器的请求头常用于注入Authorization、X-Debug-Mode、User-Agent等。Response Header修改服务器返回的响应头常用于删除X-Powered-By、添加Access-Control-Allow-Origin: *解决 CORS、或修改Cache-Control强制不缓存。Request Body修改请求体适用于 POST/PUT 请求的 JSON 或 Form Data。Response Body修改响应体这是最常用也最需谨慎的场景尤其是处理 JSON 数据。选定 Location 后Match区域才是真正的“瞄准镜”。它包含三个子项SchemeHTTP 或 HTTPS一般选Any除非你明确知道只处理某一种协议。Host这是最关键的过滤器。不要填*而要填具体的域名如api.example.com或正则^api\..*\.example\.com$。我建议用正则因为它能精确匹配子域名避免test-api.example.com和prod-api.example.com规则互相干扰。Path路径匹配。这里强烈推荐使用正则而非通配符。例如匹配所有用户相关接口写^/api/v1/users(/.*)?$而不是/api/v1/users/*。前者能准确捕获/api/v1/users/123和/api/v1/users?statusactive后者在 Charles 里可能匹配不到带 query 的 URL。提示Match 条件越精确规则越安全。我有个硬性规定每条新规则创建后第一件事就是用 Charles 的Sequence功能右键请求 → Sequence查看它是否只命中了预期的那几个请求绝不多、也绝不少。3.3 Operation 与 Value四种操作模式的实战选择逻辑Operation决定了“怎么改”它有四种模式每种都有其不可替代的适用场景Set覆盖式设置。如果目标字段不存在则新增如果已存在则完全替换。这是最常用的操作比如Set Header: Authorization Bearer abc123。Append追加式设置。只在字段已存在时在其值末尾添加内容。典型用途是向Cookie头追加一个调试 flag如Append Header: Cookie ; debug_modetrue。Delete删除字段。简单粗暴比如Delete Header: X-Frame-Options用来解除 iframe 嵌入限制。Replace替换式修改。这是处理 Body 的核心操作它要求你指定Find查找内容和Replace With替换内容。对于 JSON BodyCharles 提供了JSONPath解析器这才是专业用法。注意Replace 操作的 Find 字段如果选择Text类型务必勾选Case Sensitive和Regex。否则id: 123会被错误地匹配到user_id: 123456里。我踩过的最大坑就是没勾 Regex结果把所有包含123的数字都替换了导致订单金额全乱套。3.4 JSONPath 替换实战如何安全地修改嵌套 JSON 字段当 Location 选为Response Body且 Type 为JSON时Charles 会启用 JSONPath 解析器。这是 Rewrite 最强大的能力也是最容易出错的地方。JSONPath 语法类似 XPath但专为 JSON 设计。假设后端返回如下响应{ data: { user: { id: 123456, name: 张三, profile: { avatar: https://cdn.example.com/avatar/123.jpg, level: 5 } } }, code: 200, msg: success }你想把user.profile.level改为99正确的 JSONPath 表达式是$..user.profile.level。这里的$代表根对象..表示递归下降user.profile.level是路径。千万别写成$.data.user.profile.level因为如果后端某天调整了结构把data层去掉这条规则就失效了而$..user.profile.level具有更强的鲁棒性。Replace With 字段直接填99即可。再复杂一点如果你想把user.id改成一个随机数Charles 不支持原生函数但你可以用Regex模式配合正则表达式Find 填id\s*:\s*\dReplace With 填id: 999999。这里\s*匹配任意空白字符空格、换行、tab\d匹配一个或多个数字确保能匹配id: 123、id : 123、甚至id:\n123这些不同格式。我习惯在 Find 字段里多写几个\s*宁可宽松也不要因格式差异导致匹配失败。4. Rewrite 高阶技巧与避坑指南那些文档里不会写的实战经验4.1 多规则串联与优先级控制让 Rewrite 像写代码一样有逻辑单条 Rewrite 规则只能做原子操作但现实中的需求往往是复合的。比如你既要修改响应体里的user.id又要删除响应头里的X-RateLimit-Remaining。这时你不能指望一条规则搞定一切而要构建一个规则链Rule Chain。Charles 的规则执行顺序就是它们在 Rules 列表中的自上而下顺序。因此你需要精心设计这个顺序。一个黄金法则是Header 操作永远放在 Body 操作之前。为什么因为有些 Body 操作如 JSONPath依赖于响应头里的Content-Type是否为application/json如果先删了Content-Type后面的 JSONPath 就会失效。我通常的分组策略是第一组放所有 Header 相关规则命名如01-Header-Cleanup第二组放 Body 解析规则02-Body-Transform第三组放兜底的通用规则99-Global-Fallback。这样即使某天新加了一条规则只要把它拖到对应组里整个逻辑流就不会乱。另外Charles 支持为规则组设置Enabled/Disabled开关这相当于代码里的if/else分支。我在一个大型项目里为不同测试阶段冒烟/集成/UAT准备了三套规则组测试经理只需要点几下鼠标就能切换整个环境的流量行为比改配置文件快十倍。4.2 证书与 HTTPS 抓包Rewrite 在加密流量中的生效前提Rewrite 功能对 HTTPS 流量同样有效但这有一个绝对前提Charles 的 SSL Proxying 必须正确配置。很多人遇到“Rewrite 规则对 HTTPS 请求不生效”90% 的原因是证书问题。步骤必须严格按顺序执行在 Charles 中开启Proxy → SSL Proxying Settings勾选Enable SSL Proxying并在下方Locations列表中添加你的目标域名如*.example.com:443。注意这里必须写:443端口号不能省略。在手机或电脑上访问chls.pro/ssl下载并安装 Charles 根证书。关键一步iOS 设备安装后必须进入Settings → General → About → Certificate Trust Settings找到 Charles 证书并手动开启完全信任macOS 则需在钥匙串访问中双击证书展开Trust将When using this certificate设为Always Trust。确保客户端浏览器/App的代理设置指向 Charles 的 IP 和端口默认 8888。注意如果出现request header is too large或upstream prematurely closed connection这类错误大概率是 SSL 握手失败导致的。此时不要怀疑 Rewrite 规则而要立刻检查证书信任状态。我有个快速验证法在 Charles 的 Structure 视图中看 HTTPS 请求的图标是不是绿色的锁。如果是红色的叉说明 SSL 解密失败Rewrite 自然不会触发。4.3 常见问题速查表从报错信息反推 Rewrite 故障根源报错现象最可能原因排查步骤我的解决方案Rewrite 规则完全不生效规则组未启用或 Location/Match 配置范围过大/过小1. 检查规则组左侧的 Enable 开关是否打开2. 在 Sequence 中右键请求 →Breakpoints确认请求是否进入 Charles 流程3. 临时把 Match 的 Host 改为*看是否生效我习惯先建一个DEBUG-ALL规则组Match Host*OperationSet HeaderKeyX-Rewrite-DebugValueHIT然后在浏览器里看响应头有没有这个字段快速定位是规则问题还是代理问题JSONPath 替换后返回 500 错误JSONPath 表达式错误导致 Charles 无法解析响应体1. 在 Charles 的 Raw 标签页复制完整的 Response Body2. 用在线 JSONPath 测试工具如 jsonpath.com验证表达式3. 检查响应是否真的是 JSON 格式有时后端返回 HTML 错误页我的铁律任何 JSONPath 规则上线前必须用$.data这种最顶层的路径先测试一次确保基本解析通再逐步细化到深层字段替换后页面白屏或 JS 报错Body 替换破坏了 JSON 结构完整性如引号不匹配、逗号遗漏1. 在 Charles 的 JSON 标签页看格式化后的 JSON 是否有红色高亮错误2. 对比替换前后的 Raw Body用文本比较工具如 WinMerge找差异我永远在 Replace With 字段里把数字99写成99加引号把布尔值true写成true确保类型和原 JSON 一致绝不相信肉眼判断同一请求被多条规则重复修改规则 Match 条件重叠且没有设置优先级1. 在 Rules 列表中按住 Ctrl/Cmd 多选几条规则右键 →Edit Selected Rules2. 检查它们的 Host 和 Path 是否有交集我给每条规则的 Name 字段都加上序号如01-Auth-Token、02-User-Level并定期用 Excel 导出所有规则用公式检查 Host 列是否有重复4.4 Rewrite 与 Charles 其他功能的深度联动技巧Rewrite 不是孤岛它和 Charles 的其他功能结合能产生 112 的效果。第一个联动是Breakpoint当你不确定某个字段的原始值是什么时先用 Breakpoint 拦停请求手动记下X-Request-ID的值再基于这个值创建一条 Rewrite 规则专门针对这个 ID 的请求做特殊处理。第二个联动是Export/ImportCharles 支持将整个 Ruleset 导出为.chls文件。我把每个项目的 Rewrite 规则都存进 Git 仓库和代码一起版本管理。这样新同事入职只需导入这个文件就能获得和我一模一样的调试环境避免了口头传授的误差。第三个联动是Throttling在弱网测试中我经常把 Rewrite 和 Throttling 组合使用——先用 Rewrite 注入一个X-Network-Condition: 3Gheader再用 Throttling 模拟 3G 网速最后在前端代码里根据这个 header 做降级处理整个链路完全可控。最后一个鲜为人知的技巧是Rewrite Repeat选中一个请求右键 →RepeatCharles 会重新发送它。如果这个请求关联了 Rewrite 规则Repeat 时规则依然生效。这让我能快速验证“修改后的行为是否符合预期”而不用反复在浏览器里点刷新。5. Rewrite 在真实项目中的典型应用场景拆解5.1 前端联调场景绕过后端未就绪接口实现并行开发这是 Rewrite 最高频的应用。假设后端同学承诺三天后交付/api/v1/orders接口但前端明天就要演示订单列表页。传统做法是等或者自己写 mock server。用 Rewrite你可以这样做在 Charles 中创建规则组FRONTEND-MOCK-ORDERS。添加一条规则LocationResponse BodyMatch Hostapi.example.comPath^/api/v1/ordersOperationReplaceFind.*匹配全部Replace With{data:[{id:1,title:测试订单,status:paid},{id:2,title:模拟订单,status:pending}],code:200}。同时添加一条 Header 规则LocationResponse HeaderOperationSetKeyContent-TypeValueapplication/json;charsetutf-8。这样前端发起的任何/api/v1/orders请求都会收到你预设的 JSON 数据且 Content-Type 正确前端代码无需任何修改。更进一步你可以用 JSONPath只替换data数组而保留code和msg字段让 mock 数据更贴近真实响应结构。我坚持认为一个成熟的前端团队其 Charles 配置里至少要有 5-10 个这样的 mock 规则组它们是并行开发的基石。5.2 兼容性测试场景批量验证不同设备/系统的 UI 表现App 在不同系统上的表现差异往往源于 User-Agent 或 Accept-Language 等 header 的细微差别。Rewrite 让你可以“一人分饰多角”。例如要测试微信内置浏览器的兼容性创建规则组COMPATIBILITY-WX-WECHAT。添加规则LocationRequest HeaderMatch Host*OperationSetKeyUser-AgentValueMozilla/5.0 (Linux; Android 12; SM-S901B Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.127 Mobile Safari/537.36 MMWEBID/1234 MicroMessenger/8.0.39.2360(0x28002733) WeChat/8.0.39.2360 NetType/WIFI Language/zh_CN ABI/arm64。再添加一条LocationRequest HeaderOperationSetKeyX-Requested-WithValuecom.tencent.mm。配置完成后你在 Chrome 里访问网页Charles 就会自动把你的请求伪装成微信浏览器发出前端 JS 就能检测到WeChat字符串从而加载对应的兼容性补丁。同理你可以为 iOS Safari、Chrome for Android、甚至旧版 UC 浏览器分别建立规则组一键切换批量截图对比。这比租用几十台真机做自动化截图成本低、速度快、可控性强。5.3 安全与性能测试场景主动触发服务端防护与瓶颈Rewrite 也是安全和性能工程师的利器。比如要测试 WAFWeb 应用防火墙对 SQL 注入的拦截能力创建规则组SECURITY-SQLI-TEST。添加规则LocationRequest BodyMatch Path^/api/v1/searchOperationReplaceFindkeyword:(.*)Replace Withkeyword:admin OR 11。这样每次搜索请求都会自动注入经典的 OR 11payloadWAF 日志里就能看到拦截记录。再比如测试服务端对超大 header 的处理创建规则组PERF-HEADER-LIMIT。添加规则LocationRequest HeaderOperationAppendKeyX-Debug-DataValuea.repeat(10000)生成一万字符的字符串。然后观察后端是否返回431 Request Header Fields Too Large。这些测试都不需要修改任何一行业务代码完全在流量层面完成既安全又高效。我参与过三次大型红蓝对抗蓝队防守方的监控告警有 70% 的规则最初都是通过 Charles Rewrite 主动触发、验证后才上线的。6. Rewrite 的局限性与未来演进思考它不是万能的但足够好用6.1 Rewrite 无法解决的问题清单坦诚面对技术边界尽管 Rewrite 功能强大但它有清晰的边界承认这些边界才能用得更理性无法修改 WebSocket 帧内容Charles 的 Rewrite 只作用于 HTTP/HTTPS对 WebSocket 的二进制或文本帧无效。要调试 WebSocket必须用 Breakpoint 拦停手动编辑。无法处理 HTTP/2 的头部压缩HPACK虽然 Charles 支持 HTTP/2 代理但 Rewrite 对 HPACK 编码后的 header 修改成功率不稳定。我的建议是在调试 HTTP/2 服务时先在服务器端临时降级到 HTTP/1.1完成 Rewrite 验证后再切回。无法动态生成值Rewrite 的 Replace With 字段是静态字符串不支持变量、时间戳、随机数等动态内容。比如你不能写Replace With: timestamp: ${Date.now()}。如果真有此需求必须借助 Charles 的Scripting功能需 Java/JS 编写脚本但那已超出 Rewrite 的范畴。无法跨请求状态保持Rewrite 是无状态的它不能记住上一个请求的session_id然后在下一个请求里自动带上。这种有状态的逻辑需要 Breakpoint 手动复制粘贴或用外部脚本协调。提示当你的需求频繁触及这些边界时就是时候评估是否该引入更专业的工具了比如 mitmproxy支持 Python 脚本或自研的流量网关。但对 95% 的日常调试而言Charles Rewrite 的简洁性和稳定性依然是最优解。6.2 从 Rewrite 到自动化如何把手工配置沉淀为团队标准一条好的 Rewrite 规则不应该只存在于你的本地 Charles 配置里。我推动团队做了三件事让 Rewrite 从个人技巧变成团队资产规则文档化每个规则组都配有一份 Markdown 文档放在项目 Wiki 里说明“为什么需要这条规则”、“匹配哪些 URL”、“修改了什么字段”、“预期效果是什么”、“谁负责维护”。这避免了知识只掌握在一个人手里。规则模板化我们提炼了 5 种高频模板如API-Mock-Template、CORS-Bypass-Template、Auth-Inject-Template新同学只需填几个参数就能生成一条合规的规则大幅降低学习成本。CI/CD 集成在 Jenkins 流水线中增加一个步骤自动下载最新的rewrite-rules.chls文件用脚本解析其内容检查是否有违反安全规范的规则如Set Header: Authorization Bearer hardcode_token如果有则阻断发布。这把质量门禁从代码层延伸到了调试配置层。最后分享一个小技巧我在每条 Rewrite 规则的 Name 字段里都加上[TEAM]前缀比如[PAYMENT] Order Status Override。这样当多人协作时一眼就能看出这条规则属于哪个子系统责任归属清晰。技术工具的价值最终体现在它能否被规模化、标准化地复用。Charles Rewrite 本身很简单但围绕它建立的这套实践方法论才是真正值得传承的经验。