
改一个 User-Agent 就能让服务端返回另一套移动端页面加一个自定义请求头就能把流量切到灰度环境删掉 If-None-Match 就能绕开缓存拿到最新资源——这些操作如果还靠改代码重新发版、临时写脚本发包效率就太低了。ModHeader解决的就是这一层问题它是一个浏览器扩展把请求头修改做成了图形界面不用写代码、不用重启服务点几下就能让浏览器发出的每一个请求带上你想要的 Header也能顺手把服务端返回的响应头改掉。我第一次正式用它是为了验证一个只在特定 UA 下才出现的样式错位问题当时手边没有真机改后端又得等发版装上这个扩展之后五分钟就把问题定位了。这篇内容面向三类人一是做前端联调、经常要在多套环境之间横跳的开发二是做测试、需要构造各种边界请求的同学三是对 HTTP 协议有基本概念、想找个轻量工具验证自己想法的学习者。看完你应该能独立完成装、配、调、排这一整套流程也能知道哪些头改了有用、哪些头改了纯属白费功夫。1. 先想清楚为什么要在浏览器这一层改请求头1.1 三个真实场景看看你在不在其中场景一多环境联调。一套前端代码要对接开发、测试、预发三套后端常见做法是后端在网关层留一个路由头比如X-Env: gray前端只要带上这个头请求就会被转发到灰度集群。问题在于这个头不能写死在业务代码里否则上线前还得记得删写在构建变量里又得重新打包。这时候用扩展在浏览器端注入是最省事的做法。场景二问题复现。用户反馈页面白屏你拿到一条错误日志里面提到某个接口返回了 403。你怀疑是鉴权头没带上或者是 Referer 被拦截。想验证这个猜想最直接的办法就是手动构造一个带特定Referer、特定Authorization的请求看看服务端到底怎么回。场景三缓存与协商缓存排查。前端改了静态资源但用户那边还是旧版本你怀疑是 CDN 或者浏览器缓存策略的问题。把If-None-Match和If-Modified-Since删掉再请求一次如果拿到的是 200 而不是 304那基本就能确认是协商缓存命中导致的。这三个场景有个共同点改动只发生在请求发出这一刻不改代码、不改配置、不污染环境。这正是浏览器扩展擅长的事情。1.2 四种改头方案横向对比为什么最后选了扩展在动手之前值得把可选方案摊开对比一下这样你才知道自己选的工具处在什么位置。方案操作成本生效范围持久化适合场景改后端/网关配置高需要发版或改配置全量用户强长期策略业务代码里写死中改完要重新构建当前代码强但脏不建议开发者工具手动重放请求低但每次都要重来单次请求无一次性验证抓包工具做中间层拦截中要装证书、配规则整个系统流量中多端联调浏览器扩展极低装完即用当前浏览器强可存多套配置日常高频调试抓包工具的能力确实更强能看能改能重放但它有个天然的门槛要么得让设备走它的中间层要么得在手机上装证书。如果我只是想在桌面浏览器上验证一个 Header 的效果为了这个去折腾一整套链路性价比太低。而浏览器扩展的逻辑是我就在请求出发生命周期的同一侧动手页面该发的请求照发我只是在它出门前给它换件衣服页面本身完全无感知。再说开发者工具里的重放请求功能。它能改头但改完是一次性的刷新页面就回到原点而且它改的是重新发一次不是让页面自己发的这一次带上新头。如果你要验证的是页面渲染逻辑对某个头的响应那就必须让真实的页面请求带上去重放做不到这一点。所以扩展的定位很明确高频、轻量、单浏览器内的请求头调试。它不替代抓包工具两者是互补关系。1.3 ModHeader 站在哪一层原理层面的解释要理解它能做什么、不能做什么得先知道浏览器扩展是怎么插手网络请求的。浏览器给扩展提供了两个层面的能力。老一点的方式是监听请求生命周期事件在请求即将发出时修改请求头对象新一点的方式是基于声明式规则扩展只负责声明规则由浏览器内核去执行匹配和改写。后者性能更好、权限更收敛现在主流扩展基本都往这个方向走。ModHeader 的底层能力就落在这一层它把你在界面上填的表格翻译成一组浏览器能理解的匹配规则规则包含三要素——匹配哪些请求、动哪个头、改成什么值。这个原理直接决定了它的能力边界有几点必须提前说清楚浏览器有一批受保护的头是禁止扩展修改的。典型的比如Host、Content-Length、Connection、Upgrade-Insecure-Requests这类因为它们由浏览器内核自己管理和计算随便改会破坏协议本身的完整性。你在界面上填了这些名字保存是能保存的但发出去的请求里看不到效果——不是工具坏了是浏览器不让改。还有一些头是有条件可改的。比如Cookie某些版本允许你整体重写但如果你想追加一个 cookie就得自己把完整的 cookie 串拼出来否则容易把原有登录态干掉。Origin和Referer一般能改但改了之后服务端的跨域校验逻辑会跟着变可能引入新的报错。最后一点扩展只影响它自己所在的那个浏览器。同一台机器上另一个浏览器、同一浏览器的无痕窗口取决于配置、手机上的浏览器统统不受影响。这一点是优点也是坑后面排查章节会专门讲。2. 装好之后先把界面摸熟别急着改2.1 安装与固定装扩展的路径不用多说从扩展市场搜索名字装上就行。装完之后有个动作一定要做把它固定到工具栏。原因很实际——这个工具的核心价值是随手开关如果每次都要点两下进扩展管理页才能打开你很快就会懒得用。固定之后图标上通常会有一个角标或者变色提示表示当前有规则正在生效。这个视觉反馈非常重要我后面会讲到忘记关掉是最常见的翻车原因。第一次打开界面你会看到一个略微朴素的表格加上几排开关。不要被它的外观骗了这个工具的复杂度不在界面上在于你要改的那个头本身的语义。界面只是搬运工真正决定成败的是你对 HTTP 头的理解。2.2 界面五大区域逐个拆解我把它的界面拆成五块来看这样你上手会快很多。第一块是配置档案Profile下拉。你可以把它理解成规则集一套完整的调试配置存一个档案下拉切换即可。这个设计的价值在于场景隔离给移动端调试存一个档案给灰度环境存一个档案给 CORS 验证存一个档案。切换的时候不用逐个勾选一键换全套。我个人的习惯是按项目名 用途来命名比如某项目-移动端UA、某项目-灰度路由一眼就知道在干什么。第二块是请求头表格。三列头名称、头值、启用勾选框。每一行是一条独立规则可以单独开关。这个单独开关很关键意味着你可以在同一个档案里预置五六条规则调试时只勾选当前需要的那一条不用反复删了重建。第三块是过滤区。这里有三个维度URL 匹配模式、资源类型、请求方法。这是整个工具里最容易被忽略、但最重要的部分。默认情况下规则是全局生效的也就是这个浏览器里所有站点的所有请求都会被加上你填的头。这在调试生产环境时是很危险的你可能会把调试标记带到别的系统里去。所以我的建议是养成习惯每建一条规则就先设 URL 过滤。第四块是响应头区域。和请求头分开单独的表格。很多人不知道这里能改其实在验证 CORS、验证缓存策略、验证安全头的时候改响应头比改请求头更有用。第五块是底部的全局开关和导入导出。全局开关就是一键静音导入导出则是团队协作的入口——把配置文件导出成文件别人导入就能复现你的调试环境。2.3 请求头和响应头的区别别搞混这两个 Tab 长得很像但作用方向完全相反理解错了会浪费很多时间。请求头是你发出去的东西它影响的是服务端怎么理解这次请求。改User-Agent让服务端以为你是手机改Accept-Language让服务端返回英文页面改自定义的X-*头让网关做路由都属于这一类。这类修改的生效时机是请求发出前页面 JS 完全不知道你干了什么。响应头是服务端返回来的东西。扩展能改响应头是因为它在浏览器的响应处理环节插了一手。这里要特别注意一个前提改响应头只能改已经到达浏览器的响应它不能凭空创造响应也不能改变服务端实际执行了什么逻辑。比如你改了Access-Control-Allow-Origin表面上跨域报错消失了但这只是本地骗过了浏览器的同源策略检查服务端那边该没鉴权还是没鉴权。这个区别在安全验证时特别重要——本地改通了不代表线上是通的千万不要把调试结果当成结论。还有一个使用细节不同版本的界面措辞、菜单位置会有差异我做过的几台机器上看到的布局就略有不同但底层逻辑是一致的——都是匹配条件 目标头 目标值这个三元组。你只要抓住这个逻辑界面怎么变都能快速找到入口。3. 核心功能实操五种动作、四个参数3.1 新增、覆盖、追加、删除四种动作要分清界面上看起来只是填个名字和值实际上你可能在表达四种完全不同的意图这点必须掰开讲。新增是最常见的填一个原本请求里没有的头名和值比如加X-Debug: 1。注意如果这个头原本就存在不同工具的行为不同有的覆盖有的追加需要看具体设置项。覆盖是指这个头本来就有你要把它的值换掉。比如请求里本来就带Accept-Encoding: gzip, deflate, br你改成identity要求服务端别压缩方便你肉眼读报文。追加是指保留原有的值在后面再补一段。最典型的是Cookie如果规则实现成覆盖你的登录态会立刻掉线如果实现成追加就是新增一个 cookie。判断方式很简单改完之后看接口是不是还认得你不认得就是被覆盖了。删除是最容易搞错的一个。把值留空发送出去的通常是一个空值头HeaderName:而不是把这个头彻底去掉。这两者对服务端来说语义完全不同前者是我告诉你这个头的值是空字符串后者是这个头不存在。如果服务端代码里判断的是if (header ! undefined)那你留空反而会命中存在分支得到和预期相反的结果。所以删除这个动作一定要去实际请求里确认一下。还有一个隐藏的坑同名头出现两次。HTTP 协议里有些头是允许重复的比如Accept、Cache-Control有些则不应该重复。如果你新增了一个已经存在的头同时又没清掉原来的有些服务端会把它们拼接起来有些则只取第一个。表现出来就是我明明改了怎么服务端拿到的还是旧值排查起来很费时间。3.2 URL 过滤三个维度缺一不可这一节我建议你重点看因为过滤没配对是改了不生效和改得到处都是这两类问题的共同根源。维度一URL 匹配模式。支持通配符写法可以精确到域名、路径甚至是查询参数。这里有几个实战要点。第一协议也要写进去。你写example.com和写https://example.com在很多实现里匹配结果不一样前者可能什么都匹配不到。稳妥的写法是带上完整协议。第二端口号别漏。本地开发跑在http://localhost:3000你要是只写localhost某些实现会匹配不上带端口的地址。第三尾斜杠有影响。/api/user和/api/user/在后端是两个路由在匹配规则里也可能表现不同。第四子域名是否包含要想清楚。写*.example.com通常能覆盖所有子域但如果写example.com很可能只是精确匹配主域子域的请求就漏掉了。维度二资源类型。浏览器会把请求分类主文档、样式表、脚本、图片、字体、XHR/fetch、WebSocket 等等。这个维度的用处在于精准打击。比如你只想给接口请求XHR/fetch加调试头那就只勾这一类页面加载的图片、字体统统不受影响。这样能大幅降低副作用——你不会因为给一张图片加了Authorization头而触发一堆奇怪的错误日志。维度三请求方法。GET 还是 POST这两者往往走不同的后端处理链路。如果你想验证的只是查询类接口限定 GET 就够了。把这三个维度组合起来一条规则的匹配条件可以精确到某域名下、路径以 /api 开头、类型是 XHR、方法为 GET。这才是可以放心长期挂着的配置。注意规则是匹配即生效的没有优先级排序的概念时多条规则同时命中同一个请求可能会出现互相覆盖的情况。配置多了以后建议一条一条单独验证不要一次性开一堆。3.3 请求重定向一个容易被滥用的功能除了改头这类工具通常还提供 URL 重定向能力命中某个模式的请求直接转发到你指定的另一个地址。典型用法是把线上静态资源指向本地开发服务器或者把某个接口从生产切到本地 mock。用起来很爽但我得提醒两件事。第一重定向改的是请求目的地不是请求头。很多跨域问题在这个过程中会被放大或者改变形态原本同源的请求变成跨源了浏览器立刻抛 CORS 错误。所以如果重定向之后接口挂了先看控制台的跨域报错而不是怀疑头没配对。第二页面内的相对路径跳转会出问题。你把https://a.example.com/api/重定向到http://localhost:8080/api/接口是通了但如果返回的数据里带着相对路径的资源地址页面会拿本地地址去拼图片全挂。这个坑基本每个用过重定向的人都踩过一次。我的建议是重定向只用于临时验证验证完立刻关掉。长期使用的方案还是老老实实配本地开发环境。3.4 多配置档案的工程化管理这个功能值得单独拿出来讲因为它决定了你是随便玩玩还是真的把它当成日常工具。我见过一个很实用的做法把配置档案当成环境快照来管理。一个人负责的项目通常有四五套环境每套环境对应一组头环境标记、鉴权占位、特定 UA。全部预先建好档案日常就是在下拉框里切一下。切换的时候其他档案的规则自动失效不会互相干扰。更进一步是做导入导出。把档案导出成文件放进项目仓库的docs/debug/目录下新同事拉代码后导入一次调试环境就齐了。这个做法尤其适合那种必须带特定请求头才能跑通的项目比写十页文档管用。注意导出的配置文件里会包含你填的所有值包括那些临时粘贴进去的令牌。分享之前务必把敏感值清空或替换成占位符这个细节后面还会专门说。4. 四个典型实战照着做就能跑通4.1 伪造 User-Agent 做移动端适配验证这是最高频的用法但也是坑最多的一个值得完整走一遍。第一步先确定你要伪装成什么。不要凭印象随便编一串字符串找一个真实设备的完整 UA 复制过来。你可以在手机上访问任意一个显示请求头的网站把结果复制出来也可以从公开的 UA 列表里取。为什么强调完整因为服务端做设备判断时经常不只看Mobile这个关键字还会看版本号、渲染引擎标识。字符串不完整判断逻辑可能直接走偏。第二步配置规则。头名称填User-Agent值填完整的 UA 字符串。过滤条件里URL 匹配你的目标站点资源类型至少包含主文档否则页面 HTML 本身不会被替换你看到的还是桌面版页面。第三步刷新页面。这里有个必须做的动作已经打开的页面不会自动生效。因为页面在加载时就已经根据当时的 UA 决定了渲染什么你后来改的 UA 只对之后发出的请求有效。所以改完必须硬刷新一次不走缓存的那种刷新。第四步验证。打开开发者工具的网络面板点开任意一个请求看请求头里User-Agent是不是你填的值。如果没变先别怀疑工具按第 5 章的排查顺序来。这里面有个进阶的坑现代浏览器除了User-Agent还会发送一组客户端提示头类似Sec-CH-UA-*这样的系列头用来更精细地描述设备能力。有些服务端已经改成优先读这组头。这意味着你只改User-Agent服务端可能依然认为你是桌面浏览器。遇到这种情况需要把那组提示头也一并处理掉或者干脆把它们删掉让服务端回退到读User-Agent的逻辑。还有一个更现实的限制UA 只能骗过服务端的判断逻辑骗不过浏览器本身。页面按移动端逻辑渲染出来了但你的窗口还是桌面尺寸媒体查询依然按桌面宽度走。想看真实效果还得配合开发者工具里的设备模拟功能一起用。两个工具叠加才是完整的方案。4.2 灰度路由与自定义标记头后端在网关层做灰度靠的是一个自定义头这在很多团队是标准做法。用扩展注入的优势是只有你自己这个浏览器会带不影响其他任何人。配置上没什么难度头名按后端约定填一般是X-开头值填灰度标识。真正需要留心的是三点。第一这个头会被带到所有子请求里。如果页面里嵌了第三方统计、第三方字体它们的请求也会带上这个头。虽然大部分第三方服务不认这个头但严格来说这属于信息外泄。解决办法就是用 URL 过滤把范围限定在自己域名下。第二跨域请求会触发预检。加了自定义头之后原本的简单请求会变成需要预检的请求浏览器会先发一个 OPTIONS 询问服务端我能不能带这个头。如果服务端没配好允许的头列表请求会直接失败。这时候你会看到一堆 OPTIONS 请求返回 4xx别以为是扩展坏了是服务端的跨域配置需要补一条。第三记得关。灰度验证完忘了关第二天你会发现自己在测试环境里怎么都复现不了问题——因为你还带着灰度标记。这是我踩过的最典型的一次排查了两个小时才发现是昨天开的规则没关。4.3 跨域调试改哪些头有用改哪些是自欺欺人跨域问题几乎每个前端都遇到过。用扩展改头确实能让报错消失但你得清楚自己在做什么。改请求头Origin这个头由浏览器自动添加标识请求来源。有些服务端的跨域校验逻辑写得很宽松只要Origin在白名单里就放行。你把Origin改成白名单里的值报错确实消失了。但这说明什么说明服务端的校验逻辑依赖一个客户端可以伪造的头这本身是个安全隐患。调试时用它来验证是不是Origin校验导致的失败是可以的但别把它当成解决方案。改响应头Access-Control-Allow-Origin这个更直接本地骗过浏览器让响应能进到页面里。用来验证如果服务端加了这个头我的代码逻辑对不对是很有效的。但同样的本地通了不等于线上通。真正该做的如果你只是想定位问题改头是很好的手段如果你要修问题改头只是帮你确认了病因药还得开在服务端或者构建配置里。提示跨域调试期间打开着改响应头的规则很容易产生我这里明明没问题的误判。建议在验证完立刻关掉规则再复测一次确认问题是真的解决了还是被掩盖了。4.4 缓存与条件请求删头比加头更有用这一类是减法操作很多人想不到。浏览器请求静态资源时如果本地有缓存会带上If-None-Match对应服务端的 ETag或者If-Modified-Since对应 Last-Modified。服务端比对之后如果认为没变返回 304页面直接用本地缓存。你想确认服务端上的资源到底更新了没有把这两个头删掉再请求服务端就会返回完整的 200 响应和最新内容。如果 200 返回的内容和本地缓存一致说明服务端确实没更新如果内容不一样说明是缓存没失效。反过来你也可以加Cache-Control: no-cache到请求里请求服务端跳过缓存重新验证。注意这是请求头方向的Cache-Control和响应头方向的同名头语义不同别混用。实操中还有一个技巧把Accept-Encoding改成identity让服务端返回未压缩的内容。这样你在网络面板里看到的响应体就是明文不用担心解压问题。这个技巧在读一些被压缩成乱码的响应时特别管用。5. 排错实录改了不生效按这个顺序查5.1 七步排查法从外到内我把这几年遇到的不生效问题整理成一个固定顺序照着走基本能覆盖九成情况。第一步看全局开关。扩展图标上有没有状态提示很多人是在一个档案里配好了规则但全局静音开关还开着或者当前停在另一个空档案上。这是最简单的错误也是发生率最高的。第二步确认规则本身的勾选框。每条规则前面有独立的启用勾选配好之后忘了勾等于没配。第三步核对 URL 匹配。把匹配模式复制出来和网络面板里那条请求的完整地址逐字对比。重点看协议、端口、尾斜杠、子域名。我遇到过一次是因为页面用的地址带了www规则里没写结果怎么都不生效。第四步看资源类型。如果你只勾了 XHR但你要改的其实是主文档请求那自然不会生效。这个维度的复选框很容易被顺手带过。第五步判断是不是受保护的头。前面提过Host、Content-Length、Connection这类由浏览器自己管理的头改了也不会出现在实际请求里。遇到这种情况不用继续折腾换个思路解决。第六步看是不是被页面本身拦截了。如果站点注册了 Service Worker页面的请求可能先经过它。有些 Service Worker 会自己构造请求这时候扩展的规则可能不按预期生效。验证方法很简单开一个无痕窗口默认不加载 Service Worker再试一次如果无痕里生效了基本就能定位。第七步看有没有多个扩展打架。装了多个改头类扩展规则可能互相覆盖。暂时禁用其他同类扩展只留一个再测。5.2 常见问题速查表现象最可能的原因处理方式规则配好了请求里完全看不到全局开关没开 / 档案选错检查图标状态切换档案部分请求有部分没有URL 匹配过窄或资源类型没勾全放宽匹配模式勾选更多类型改了但服务端拿到的还是旧值头重复了新增而非覆盖确认原头是否已存在改用覆盖改了Host之类的头没反应属于浏览器保留头禁止修改换方案别在这条路上耗页面样式突然全乱规则命中了静态资源请求加资源类型过滤排除图片/字体昨天还好好的今天全挂了规则忘了关或者站点换了域名逐条关闭规则做二分定位接口开始报跨域错误新增自定义头触发了预检检查服务端允许的头列表手机上看不到任何效果扩展只作用于当前浏览器手机端需要另想办法验证5.3 六个只有踩过才知道的细节细节一改完必须刷新而且最好是硬刷新。已经加载的页面不会因为你改了规则就重新发请求所有的修改都只对下一次请求有效。细节二头名称不区分大小写但值的空格要命。Content-Type写成content-type没问题但值里多一个空格比如application/json某些严格的服务端会直接拒绝。从别处复制粘贴的时候特别容易带进来这个空格。细节三中文值要确认编码。有些自定义头你想塞中文进去比如调试标记写个中文说明。这时候要么确认扩展做了编码处理要么就老老实实用英文和数字。我见过因为头值里带中文导致整个请求被服务端拒掉的案例。细节四无痕窗口的扩展是单独开关的。默认情况下扩展在无痕窗口里不启用需要手动去扩展详情页勾选允许在无痕模式下运行。这个设置藏得比较深不知道的话会以为是工具失效。细节五规则是按标签页还是全局生效要看具体实现。有的版本允许仅当前标签页生效有的则是整个浏览器生效。如果你在多标签同时调试不同环境这个区别会直接影响结果。细节六浏览器重启后规则可能还在。这是好事也是坏事。好在不用每次重配坏在容易忘。我的做法是每周清一次档案把不再需要的规则删掉保持列表干净。6. 安全边界与团队协作几个必须守住的底线6.1 令牌不要长期存在配置里这个工具最方便的用法之一是把鉴权令牌直接填进请求头规则里然后所有请求自动带上。方便是真的方便风险也是真的。第一配置文件是明文存储的。你填进去的令牌会原样存在浏览器的扩展数据里。如果这台机器还有其他人用或者你哪天导出配置文件发给别人令牌就跟着走了。第二令牌通常有过期时间。你今天填的明天可能就失效了然后你会花时间去排查为什么接口突然 401最后发现是令牌过期。这种问题排查起来毫无技术含量但很浪费时间。第三它绕过了正常的鉴权流程。你手动塞一个令牌进去实际上是跳过了登录态管理。如果这个令牌的权限比你的账号高比如误用了管理员的你做的任何操作都会以那个身份记录在日志里出了问题说不清。我的做法是需要临时验证时填验证完立刻清空那一行的值只保留头名称和结构。下次要用再粘一次多花十秒钟省掉一堆麻烦。6.2 导出分享前先做脱敏团队里共享调试配置是个好习惯但共享前必须过一遍脱敏流程。具体检查这几项所有Authorization类头的值、所有Cookie类的值、所有带令牌的查询参数、所有内网地址。把真实值替换成明显的占位符比如YOUR_TOKEN_HERE并在文件里加一行注释说明需要替换哪些字段。我见过一次比较尴尬的情况有人把导出的配置直接提交到了公开仓库里面带着一台内部服务的地址和一个长期有效的令牌。虽然最后处理掉了但这个过程本身完全可以避免。6.3 只在自己有权限的环境里用这一点不用展开太多但必须明确这个工具的能力是伪造请求用在自己负责的、有授权的环境里做调试是正常工作用在任何你没有明确授权的系统上性质就完全变了。做技术的人对这条线要有清晰的认识不要因为技术上能做到就越界。6.4 上线前的那次检查我在实际项目里养成的一个习惯值得分享每次准备演示或者提测之前先把这个扩展的全局开关关掉然后再走一遍主流程。原因很实在。你调试期间挂着的规则可能让某些本该暴露的问题被掩盖了。比如你一直带着灰度标记测试环境的正常路径根本没走到比如你一直改着Origin跨域问题在本地从来没出现过。等到别人用干净的浏览器一跑问题全冒出来那时候再回头查成本高得多。用干净的浏览器环境复测一遍这个动作花不了两分钟但能省掉很多来回。最后再分享一个小技巧如果你经常需要在几套环境之间切换可以把每个环境的规则做成独立档案并且给档案起一个带前缀的名字比如统一用项目简称-开头。这样下拉列表里它们会排在一起切换的时候不用在十几个档案里找。听起来是个很琐碎的优化但一天切二十次的时候这点顺手程度差别挺明显的。