跨域,这两个字放在Web开发里,几乎每个前端都跟它打过架。但你要是以为跨域只是浏览器的同源策略那点事,就小看它了——后端要配CORS、网关要转发、本地调试要挂代理、甚至FPGA工程师设计跨时钟域时,也在处理属于他们那个世界的"跨域"问题。这次我就把几个典型领域的跨域方案串起来聊一聊,既有前端最常用的JSONP和CORS、PHP后端的跨域处理,也有Fiddler抓包代理的配置思路,顺带讲讲硬件设计里多bit信号跨时钟域那套处理逻辑。适合刚入坑的开发者,也适合想系统梳理一遍的在职工程师。
1. 跨域到底是什么:一次把概念说清楚
1.1 同源策略是根因
在Web世界里,两个页面或接口是否同源,取决于三要素:协议、域名、端口。也就是说,http://a.com:80/page.html和https://b.com:80/page.html,协议不同,跨域;http://a.com:8080和http://a.com:9090,端口不同,也算跨域。浏览器为了安全,默认不允许一个源的页面去读取另一个源的响应结果,这套机制叫同源策略。
我习惯把它类比成小区门禁:你家在A小区,想进B小区拿点东西,可以,但B小区必须先确认你是谁、你想干嘛、门卫开不开心放你进。浏览器就是那个严格的保安,路由器和服务器可以在物理层面把请求送进去,但保安不把结果给你,你就拿不到。
同源策略针对的是"读取",不是"发送"。很多人第一次遇到跨域时特别容易懵:明明后端日志里显示请求到达了,数据库也写了,但前端控制台就是报错,收不到任何响应。这是因为请求本身发出去了,服务器也处理了,只是浏览器在响应阶段发现响应头里没有允许跨域的声明,于是直接把响应拦截了。这个认知非常重要,因为排查方向的差异会导致你花几个小时代码都没动对地方。
1.2 跨域不等于浏览器出错
再补充一个容易混淆的点:跨域报错不是页面本身的问题,更不一定是接口的bug。它是一套安全机制在正常工作。换句话说,如果你把浏览器换成curl,同一个地址直接请求,大概率能正常拿到返回结果。因为curl没有同源限制,它不做跨域校验。
所以我排查问题时,第一步永远是先在浏览器地址栏直接打开接口地址,或者在终端用curl测一下。如果curl能正常返回JSON,而页面里fetch报跨域错,那就能确定问题出在浏览器的同源限制上,接下来只需要去补服务端响应头或调整请求方式。
1.3 常见的跨域场景
- 前后端分离:开发时前端跑
localhost:3000,后端跑localhost:8080,端口不同,天然跨域。 - 本地联调:本地前端页面向线上环境或别人的测试机发请求。
- 第三方接口:比如接地图、支付、天气服务,域名、协议都跟你的项目对不上。
- 静态资源服务:从CDN加载字体、图片、脚本时,如果服务端没配好CORS,加载可能受限。
搞清楚这些场景后,再看解决方案就很清晰了:要么让浏览器认为你是合法访问,比如CORS;要么绕开同源策略施加的对象,比如JSONP和代理转发;要么在本地调试阶段临时修改响应头,比如Fiddler劫持响应。这就是接下来要展开的内容。
2. 前端跨域解决方案:从JSONP到CORS
2.1 JSONP:古老但还没被彻底淘汰的方案
JSONP全称是JSON with Padding,核心思路是利用<script>标签不受同源策略限制的特点。因为<script src="...">加载JS脚本时,浏览器不会拦截响应,只要把数据包在一个函数调用里返回,页面就能通过回调函数拿到数据。
先看后端怎么配合。假设你用PHP写了这样一个接口:
<?php $data = ['name' => 'zhangsan', 'message' => '跨域测试']; $callback = $_GET['callback'] ?? ''; if ($callback) { header('Content-Type: application/javascript'); echo $callback . '(' . json_encode($data) . ');'; } else { header('Content-Type: application/json'); echo json_encode($data); }前端就动态创建一个script标签请求过去:
function jsonpRequest(url, callbackName, success) { const script = document.createElement('script'); script.src = url + '?callback=' + callbackName; script.onload = () => { document.body.removeChild(script); }; window[callbackName] = success; document.body.appendChild(script); } jsonpRequest('http://api.example.com/user', 'handleUser', (data) => { console.log('拿到数据', data); });优点是兼容性好,老版本浏览器、奇葩内置浏览器都能用。缺点是只能发GET请求,没法用POST、PUT、DELETE;而且回调函数挂在window上,如果第三方接口被攻破,攻击者可以自定义回调影响你的页面,存在安全隐患。
现在JSONP已经不再是主流,但一些老系统、第三方开放平台(尤其支付、用户中心类)还在用它。我遇到过一家银行服务商的接口只支持JSONP,因为它的业务方都是老门户网站。所以这个方案还是值得掌握的,不一定要精通,但看到callback=xxx的形式,心里得有数。
2.2 CORS:现代跨域的默认选择
CORS(Cross-Origin Resource Sharing)是W3C规范,也是目前最标准的跨域方案。它通过在HTTP响应头里添加特定字段,告诉浏览器"这个接口允许某个源访问,放心把数据交给页面吧"。
最简单的响应头是:
Access-Control-Allow-Origin: **表示允许所有域访问。实际项目中出于安全考虑,不会轻易用*,而是写成具体的源,比如:
Access-Control-Allow-Origin: http://localhost:3000如果请求还带了自定义头(比如Authorization),浏览器会先发送一个OPTIONS预检请求,询问服务器是否允许这次请求。服务器需要回复:
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 86400其中Access-Control-Max-Age表示预检结果可以缓存多少秒,设置为86400可以避免每个请求都来一次预检,减少明显的双重请求开销。
CORS既支持GET,也支持POST之类的复杂请求,还能携带Cookie和凭证。但要注意,携带凭证时Access-Control-Allow-Origin不能写成*,必须写具体源,否则浏览器会拒绝。
2.3 其他方案和取舍
要说清楚前端跨域方案,不能只知道JSONP和CORS。有些场景下,这两者都不合适。
postMessage:适合两个iframe之间、iframe与父页面之间通信。两个页面即使不同源,也可以通过window.postMessage安全地传递数据。麻烦的是需要双方都写监听逻辑,不适合对接任意第三方接口。document.domain:仅适用于主域名相同、子域名不同的情况。比如a.example.com和b.example.com,两边都设置document.domain = 'example.com',就能把同源判定放宽。缺点是必须修改双方页面代码,而且现在浏览器对它的限制也越来越严。WebSocket:它本身不受同源策略限制,因为WebSocket的连接不是通过XHR/fetch,而是独立的协议。如果业务方都支持,用WebSocket做跨域通信也是一种方案,常用于实时推送场景。- 代理转发:让同源的C端服务器去请求远程接口,再把结果返回给页面。这是工程上最推荐的方案,因为JSONP只能GET且有安全风险,CORS又需要服务端配合,而代理只对浏览器这一侧暴露同源入口,远程接口不需要改动。
我在实际项目里最常用的其实是代理转发。前端页面访问/api/xxx,Nginx或Node中间层再把/api/xxx代理到远程地址。这样代码里没有跨域逻辑,生产环境也不需要CORS那种开放策略,整个链路干净得多。
3. 后端跨域:不只是加两个响应头
3.1 后端的职责边界
很多前端同学以为跨域是纯前端问题,其实跨域能不能解决,最终话语权在后端。浏览器只认响应头,你前端代码写得再花哨,服务器不让你读,你就是读不到。所以做后端接口的同学,至少要清楚CORS响应头怎么加、什么时候需要处理OPTIONS预检、如何动态决定允许哪些源。
我见过一种很常见的翻车现场:后端开发图省事,header('Access-Control-Allow-Origin: *')直接怼上去,结果前端一旦把请求的credentials设为include,浏览器立刻报错,说通配符不能跟凭证一起用。其实需求只是让自家两个域名互调,用*既不安全也会炸,改成一个白名单逻辑才是正解。
3.2 PHP后端的跨域实战
如果你在用PHP写后端,最直接的方式是在入口文件(比如index.php)或者公共控制器里统一加响应头。注意点有这几个:
- 需要同时处理
OPTIONS请求。浏览器在发送复杂请求前会先发一个OPTIONS,此时后端应该返回204或200,并附上允许的方法和头,不再继续往下执行业务逻辑。 Access-Control-Allow-Origin不要硬编码成*,最好根据请求里的Origin头动态判断。比如只允许自己的几个域名。
一个可用的示例:
<?php $allowedOrigins = [ 'https://admin.example.com', 'https://portal.example.com' ]; $origin = $_SERVER['HTTP_ORIGIN'] ?? ''; if (in_array($origin, $allowedOrigins, true)) { header('Access-Control-Allow-Origin: ' . $origin); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With'); header('Access-Control-Max-Age: 86400'); } if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit('preflight ok'); }注意,Origin头是浏览器发给服务器的,虽然可以被伪造,但它本来就是给CORS白名单用的,不能只凭这个头做用户身份认证。身份认证还是要依赖Authorization、Cookie这些凭证。CORS白名单只是决定"允许哪个页面读取响应"。
如果你用的是Laravel、ThinkPHP这类框架,建议在中间件里统一处理,而不是每个控制器里重复写。免得以后要改白名单时,满项目找header(。
3.3 网关或反向代理层的跨域配置
有些场景中,后端接口不在你手里,可能是其他团队维护的,甚至部署在另一个域名。这时候可以在Nginx反向代理层把跨域处理掉。好处是后端不用动代码,坏处是你要确保Nginx配置能覆盖所有主导场景。
一个常见的Nginx配置片段:
location /api/ { add_header 'Access-Control-Allow-Origin' '$http_origin' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always; if ($request_method = 'OPTIONS') { return 204; } proxy_pass http://backend_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }这里用了always关键字,确保即使后端返回404、500时,这些响应头也能带上,否则浏览器会在报错响应里得不到CORS头,导致真实错误信息被吞掉,排查起来特别痛苦。
另外要注意,如果后端本身已经设置了同名的响应头,且Nginx配置里也有,会出现重复头。可以把后端的配置去掉,或者让Nginx不处理该接口,避免头字段冲突导致浏览器解析出两个不一致的值。
3.4 后端跨域的几个大坑
后端跨域踩坑比前端更隐蔽,我总结几个高频雷区:
- 预检请求被当作普通POST处理。有些后端在收到
OPTIONS时去跑业务逻辑,返回200且带业务JSON,但没带Access-Control-Allow-Headers,浏览器直接报"请求头不被允许"。更诡异的是接口看起来通了,控制台却报错,前后端互相甩锅。 Allowed-Headers漏了Content-Type。很多人加头的时候只写Authorization,忘了Content-Type。前端一旦用Content-Type: application/json发请求,预检必失败。- Cookie跨域时
SameSite干扰。浏览器从http://a.com请求http://b.com并携带Cookie,如果Cookie设置了SameSite=Lax,跨站请求可能不带Cookie。这已经超出CORS范围了,但排查跨域时非常容易混在一起。 - 多个来源需要动态放行,不能写死一条。最简单的方式是维护一个Origin白名单,动态输出,但注意有了白名单后别忘了处理"不在白名单里的源"要是什么表现,一般是不输出
Access-Control-Allow-Origin,让浏览器自己拦截即可。
4. 调试跨域的利器:Fiddler代理配置跨域
4.1 为什么需要代理来调试跨域
日常开发中,你没法要求每个第三方接口都马上给你配CORS。或者你在本地调试一个老项目,后端部署在测试环境,但测试环境的Nginx配置有历史包袱,动一下都要走发布流程。这时候用一个本地代理工具,比如Fiddler,把响应头临时改掉,是一种特别高效的调试手段。
Fiddler本质上是HTTP/HTTPS代理。你把它设置为系统代理后,浏览器发的所有请求都会经过它;Fiddler能在请求发送前、响应返回后执行自定义脚本,这样就能往响应里注入CORS头,模拟服务器已经允许跨域的效果。
我自己用下来最大的感受是:用Fiddler改响应头,不是用来糊弄线上问题的,而是为了把前端逻辑先跑通。等前端验证无误后,再推进后端或Nginx补上正式配置,避免为了等接口配置浪费开发时间。
4.2 Fiddler代理配置跨域详细步骤
先说明,Fiddler是很老牌的工具,新版Fiddler Classic免费版在Windows上依然好用。配置步骤大致如下:
- 打开Fiddler,进入
Tools -> Options -> Connections,勾选Allow remote computers to connect,确认代理端口为8888。如果只是本机调试,不勾选远程连接也行。 - 确认系统代理已开启。Fiddler启动时会自动把系统代理设置为
127.0.0.1:8888。如果不生效,可以手动在Windows浏览器代理设置里检查。 - 调试HTTPS接口时必须开启HTTPS解密。因为不解密的话,Fiddler看到的是加密流量,没法改响应内容。在
Tools -> Options -> HTTPS里勾选Decrypt HTTPS traffic,并按提示安装并信任Fiddler证书。 - 打开
FiddlerScript标签页,在OnBeforeResponse方法里写一段注入响应头的代码。比如:
if (oSession.ResponseCode >= 200 && oSession.HostnameIs("api.example.com")) { oSession.oResponse.headers.Add("Access-Control-Allow-Origin", "*"); oSession.oResponse.headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); oSession.oResponse.headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization"); }其中HostnameIs("api.example.com")可以换成你要调试的域名。改完脚本后点击Compile Script,再刷新页面触发请求,看响应头里是不是多了这3行。
- 如果想模拟春后端直接返回固定数据,可以用
AutoResponder标签页。把URL匹配规则拖进去,比如/api/user,然后选择返回一个本地文件或直接编辑好的响应体。这种方式适合后端异常断服又急着要界面效果的时候。
注意,修改完FiddlerScript后,如果没生效,先看右侧Log面板是否有脚本编译报错。Fiddler对lua语法比较敏感,比如英文分号缺失、方法名拼错都会静默失败,这点我踩过很多次坑。
4.3 代理跨域时的注意事项
- Fiddler一旦开着系统代理,所有本机浏览器的流量都会经过它,包括没有跨域问题的请求。改完记得关掉Fiddler,或者关闭系统代理,否则后续请求慢半拍。
- 手机联调时,需要让手机和电脑处于同一局域网,手机的网络代理设为电脑IP加8888端口,同时电脑防火墙放行8888。Fiddler的远程连接勾选也要打开。
- HTTPS证书不受信任时,浏览器会提示"您的连接不是私密连接"。需要在手机上安装Fiddler根证书,或者直接绕过。真机调试时建议单独安装。
- AutoResponder匹配规则写得太宽,会把正常接口也重定向掉。最好限制规则到具体路径和HTTP方法。
- 放开"Access-Control-Allow-Origin: *"只会影响当前调试设备的浏览器,不会影响线上服务,这一点可以放心,但也要注意别把线上数据库写坏了——Fiddler只是改了响应头,请求该怎么写还是怎么写。
5. 另一个"跨域":多bit信号跨时钟域处理
5.1 什么是跨时钟域(CDC)
聊完Web,再跳到一个完全不同的领域。在数字逻辑设计里,比如FPGA和ASIC,也有一种"跨域"问题,叫跨时钟域(Clock Domain Crossing,CDC)。芯片内部可能同时存在多个时钟,比如CPU主频是1.2GHz,外设总线是100MHz,以太网PHY是25MHz,这些模块之间需要传输数据,就从源时钟域跨到了目标时钟域。
为什么这很难?因为寄存器是靠时钟边沿采样的,当你用100MHz的时钟去采一个由200MHz时钟产生的信号时,这个信号的变化时刻对于100MHz时钟来说完全不可预测。如果信号刚好在建立时间或保持时间窗口内发生跳变,触发器就会进入亚稳态——输出既不像0也不像1,甚至会在高低电平之间振荡一段时间,之后才随机稳定下来。这种不确定状态如果被后面的逻辑采到,整个电路的行为都可能错乱。
把跨时钟域比作两部帧率不同的摄影机:一部每秒拍60帧,一部每秒拍24帧。你想把前者的画面抽给后者,却没法保证某一帧正好是画面切换的完整瞬间,你可能会截到半张脸、半条腿。数字电路里的亚稳态,就是截到了"量子叠加态"。
5.2 多bit信号不能直接打拍
单bit信号跨时钟域有一个经典方案:用两级触发器连打两拍,把稳定后的信号传到目标时钟域。因为亚稳态大概率在第一级就出现,但第二级寄存器采到的往往是已经稳定的值。这个方案对单bit信号足够用。
多bit信号就没这么简单了。假如源时钟域有个4bit计数器,值从0111变为1000,在极短时间里每个bit跳变可能有先后顺序。如果把这4个bit分别用两级触发器同步到目标时钟域,目标时钟采样时可能正好采到1111或0000这样的中间态,然后整个模块就算错了。各个bit独立打拍根本无法保证同步到达,这就是"多bit信号直接打拍会炸"的原因。
那么多bit信号到底怎么跨时钟域?核心原则只有一个:不要试图让多个bit在目标时钟域"恰好同时"被采到,而是要让目标时钟域确定性地知道"哪一刻数据是稳定的,可以取了"。
5.3 多bit信号跨时钟域的常见方案:FIFO、握手与dmux
第一种方案是异步FIFO。大量连续数据流跨时钟域时,异步FIFO是最稳的选择,它通过把写指针和读指针各自用格雷码编码后同步到对端时钟域,实现多bit指针的安全传递。格雷码的好处是相邻两个值之间只有1bit变化,可以当成单bit信号逐位同步,不会出现中间态。如果你要传的是真正无关的多bit数据总线,就用FIFO承载。
第二种方案是握手协议。适合传输少量控制信号,比如读寄存器配置、使能某条通路。源时钟域先在数据总线上放好数据,然后拉高请求信号,并等待目标时钟域应答。目标时钟域看到请求后,用自己时钟采集数据总线,然后拉高应答信号。源时钟域看到应答后再拉低请求,一次传输结束。这样数据在请求信号同步的掩护下,目标时钟域采到的就是稳定数据。
第三种方案常被称为dmux方式,它特别适用于某些多bit控制信号,想要在面积和延迟之间做平衡的场景。思路是把多bit数据的锁存和同步分开处理:先让源时钟域把多bit数据锁存到一个专用寄存器组,然后通过请求/应答握手把数据有效信号同步过去,最后目标时钟域在有效信号到来时,将整组数据一次性采入。为了避免数据总线自身的不同bit先被采到,设计中往往会让数据保持足够长的时间,并且让有效信号晚于数据稳定后再跨域。
这样讲可能有一点抽象,我补一个实际工程里的印象:我曾经在FPGA里做一组寄存器配置,4个bit,由慢时钟域(100MHz)写,快时钟域(400MHz)读。直接用两级触发器逐bit同步很容易偶尔读出配置错误,导致某个外设参数跳变。后来改成握手加有效信号同步的方式,用请求信号让地址和数据稳定下来,数据在快时钟域被有效信号锁存后才使用,问题就消失了。这种结构带宽不高,但可靠性和资源占用都很好,适合配置类信号,不适合大数据流。
5.4 跨时钟域设计的实践经验
- 能用单bit同步解决的,绝不拖到多bit。比如使能信号尽量压缩成一条脉冲,不要传一个状态编码。
- 连续变化的计数器尽量用格雷码。比如FIFO指针、状态机的顺序遍历,避免多bit同时翻转。
- 每个多bit数据总线至少要有一个有效的"数据有效"信号,且这个有效信号要遵守和数据的握手关系,不能想当然。
- 同步到目标时钟域后的信号,不能再作为组合逻辑反馈会源时钟域,否则可能形成环,导致时序收敛失败。
- 在SDC/SDF约束时,跨时钟域路径要单独处理。如果工具不知道是异步路径,它会按同一个时钟来约束,给你报一堆STA违规。一般用
set_false_path或set_clock_group -asynchronous来声明。
这些经验不是空话。我自己踩过的一个例子是:第一次设计异步FIFO时,把读指针的格雷码在写时钟域打了三拍之后直接拿来当地址用,导致FIFO空满状态偶尔判断错误,调试了两三天。后来老实按标准方案,先把指针同步后再比较,并且对满/空信号做额外打拍,问题立刻消失。跨时钟域问题一旦出现,不是复现一次就能定位的,它可能要靠几十万次随机时序才暴露一次,所以设计时必须比平时多留一倍的安全余量。
6. 常见问题与排查技巧实录
6.1 Web跨域问题排查速查表
我每次遇到跨域报错,都会拿出下面这个表逐一对照:
| 现象 | 可能原因 | 排查方式 |
|---|---|---|
报错里提到Access-Control-Allow-Origin | 服务端没返回CORS响应头 | 用curl看响应头,确认有没有该字段 |
| 请求方法为OPTIONS且被拦截 | 预检请求未正确响应 | 确认后端能处理OPTIONS且返回2xx |
| 带Cookie或Authorization时仍失败 | Allow-Origin用了* | 改成具体源,并设置Allow-Credentials |
| 后端能看到请求,前端却收不到数据 | 浏览器在响应阶段拦截 | 优先查看响应头,而不是看接口是否执行 |
| GET正常,POST失败 | 可能是复杂请求触发预检 | 检查Access-Control-Allow-Headers是否包含实际请求头 |
提示blocked by CORS policy但响应头齐全 | 可能有两个CORS头冲突 | 打开浏览器的网络面板,看是否同一个头出现了多次 |
这里我特别想强调第4条。我曾经帮一个同事排查线上问题,他一直认为是前端缓存导致拿不到数据,反复清缓存、刷新,结果后端日志显示接口已经正常返回。最后我打开Network面板,发现请求是成功发出、成功返回的,只是响应头里没有Access-Control-Allow-Origin,浏览器直接把JSON数据丢掉了。跨域问题一定要先看响应头,再看业务逻辑。
6.2 Fiddler调试跨域的典型问题
Fiddler改了半天响应头不生效,有几种常见情况:
- FiddlerScript编译了,但没刷新页面。旧请求已经被浏览器缓存了预检结果,直接发简单请求可能不会重新拉响应头。建议清缓存或者加时间戳参数。
- 匹配条件写错了。
oSession.HostnameIs("api.example.com")只匹配主机名,如果你请求的是api.example.com:8080,端口不一致也会漏掉。可以用oSession.URLContains("api")这种更灵活的匹配。 - 打开HTTPS解密后,浏览器证书报错。很多时候是因为没有安装Fiddler根证书,或者受信任的根证书存储没勾全。重新安装一遍根证书,并重启浏览器。
- AutoResponder和FiddlerScript同时生效,AutoResponder优先返回本地内容,导致脚本看不到真实响应。建议两个功能只使用一个。
如果你用的是macOS,Fiddler Classic已经变成付费方案,也可以考虑用Proxyman或Charles,配置思路类似——都是设置代理、抓包、改响应头。
6.3 跨时钟域问题的排查要点
在硬件领域,跨时钟域问题不像Web报错那么直观,常见的线索是仿真波形中出现不定态X,或者设备跑一段时间后偶发错误。排查思路一般是这样:
- 先检查设计里有没有违反单bit同步规则的路径。用
report_cdc或类似工具跑一遍CDC检查报告,定位所有跨时钟域路径。 - 对每个多bit数据流,确认是否采用了FIFO、握手、格雷码等正确方案。如果发现有多个bit直接穿过两级触发器,基本就是隐患。
- 看时序约束里有没有把异步时钟设为异步组。如果约束缺失,工具可能把无关时钟路径按同步路径收敛,导致真实硬件时序违例。
- 用仿真做随机时序验证,给源时钟和目标时钟设置不同的初始相位、轻微的频率漂移,看信号是否会出现亚稳态传递。这种随机化验证比单一频率仿真有效得多。
我用一个朴素的原则来判断方案是否可靠:如果这个信号在目标时钟域被使用,那么我从源时钟域拿到的每一个bit,必须能在同一拍被稳定地采到。任何依赖"多个bit刚好同时跳变再刚好同时被采到"的设计,都是定时炸弹。
最后分享一点个人经验
做Web时间久了会发现,跨域问题真正难的不是配哪些参数,而是理清楚请求链路里每一个环节各自的职责。前端决定要不要带凭证、怎么发请求;后端决定允不允许外部读、允许哪些源;网关或代理负责把策略落地;本地调试工具负责临时模拟。任何一个环节理解偏了,都会在问题排查时绕远路。跨时钟域也是一样,关键是把"你有一个信号,我要稳稳地用它"这件事想明白——数据什么时候稳定,怎么告诉目标域可以采样,这些问题想透了,不管世的方案是JSONP、CORS、FIddler还是异步FIFO、握手协议、dmux结构,本质上都是在解决同一个问题:让信息在两个不同规则的世界之间可靠地交换。
如果实在被跨域问题卡住,就拿Fiddler把完整请求链路抓一遍,从请求头看到响应头,很多问题在响应的那一瞬间就已经原形毕露了。