被cors-post-json坑到哭的经历

事情是这样的,列位看官,请听我娓娓道来。话说我们前端开发同事前几天跟我说,后端post接口在PC上可以,在手机上却不行,提示什么“NetworkError”之类。我一看还乐了,这不就是网络问题吗,以为是类似那种“重启就好”的问题。然后,虽作为后端开发人员,偶还是不服,硬要自己写几行ajax请求来弄个水落石出。写好后,先在PC上测试,木有问题,打开手机浏览,居然也木有问题,乐坏了呀,还是俺水平高。然后前端说是某个接口手机上不行,那我就试试那个接口呗,那个接口是要post json数据,我刚刚post的数据格式不是json,于是我换测试那个接口,果不其然,请求失败了。于是我想在手机上调试js代码,当然这过程也是一番痛苦的经历,查看网上的各种工具,各种折腾都看不到console.log的信息,于是一转念,您不让看log的信息我就alert出来行不行?行,于是就这么办了。alert出来的信息是:“readyState:0, status:0,……”,于是去查查这个状态是啥意思,都说是未初始化,即没有成功open url。在PC上打开firefox,试试请求,居然也失败,于是想想可能是浏览器的问题,然后手机上装Chrome,居然请求成功,所以初步结论是:不是前端说的手机上不行,PC上行,而是Chrome可以,firefox不可以。这回就可以在PC的firefox上调试了,不需要模拟手机或者真机上调试,又美了一阵。

大坑在后头呢,折腾着折腾着,一天时间就过去了,从调试控制台看,每次在post json数据请求前,都先发送一个OPTIONS请求,这个请求也就preflight request,目的就是探探服务器端是否允许接下来的请求,当然如果不是post json数据并且跨域这样的复杂请求,浏览器是不会发送OPTIONS请求的,具体何时发送这个请求,各位可以网上找找看。对于这个OPTIONS请求,服务器端已经进行了过滤,返回结果如下:

调试也可以看到返回的HTTP状态码是200,表示OK的,但firefox却罢工了,不继续发送真正的post请求,Chrome却愉快地继续发送post请求。见鬼啊,firefox是干嘛的,我各种设置检查也都没有问题呀,该给你权限的都给了呀,还通配符给你呢。

实在没有办法了,我真想剖开firefox的内脏,看看它心里是怎么想的,是如何对待这个返回200的OPTIONS请求,是如何决定不发后面的post,是在哪里把readyState设置为0的。于是我git上找了firefox源码,看XMLHttpRequest和CORS部分的代码,也看到了发送OPTIONS请求的地方,并且也看到了检查返回的各种allow首部的设置,看不出啥问题,firefox代码里面设置各种status的值也把我绕晕了都。firefox代码里面有测试代码,是Javascript代码,看了各种post请求的测试,都不是json数据,测试json数据时,只有get请求,这让我有点怀疑,firefox是不是不支持跨域post json数据呢,如果不是跨域post json数据倒是没问题,因为我们使用antd开发的同学在前端设置代理后,哪种浏览器都可以正常post json数据,因此能想到的就是“跨域post json数据”这个问题,是不是firefox没有解决?网上各种搜啊搜,搜遍了也没有遇到真正能解决这个问题的办法,firefox源码也不想看了,想想还是让前端同学设置代理算了呗……

不知道怎么回事,平时不用IE的俺突发奇想,拿IE来试试吧,结果呢,出错了:

说得很清楚啊,Access-Control-Allow-Header没有content-type,靠,我都通配符*了,还设置这个干嘛。但是浏览器却不是这么看问题的,于是修改服务端代码,也就是更上面那个图,把*通配符改成画红线的“content-type”,再试试,可以了,再打开firefox刷新,居然也发post并请求成功了,崩溃啊。后来,我们的delete,patch接口也出现问题了,都是通配符惹的祸啊,于是后端针对OPTIONS请求改成如下图所示代码,就OK了。

实话说,俺不是前端开发人员,不过在这次排查问题的过程中,翻了很多网上的资料,也学到了很多东西。顺便再说下,Access-Control-Allow-Credentials这个返回头设置为true的话,表示允许浏览器发送cookie,因为发送cookie时服务器还要检测是否是该站点的cookie,因此这个为true时,Access-Control-Allow-Origin也不能设置为通配符,各种制衡看到了吧。Access-Control-Max-Age设置OPTIONS请求缓存的最大时间,如果不缓存,每次post复杂(简单请求不涉及OPTIONS)请求时都先发送这么个OPTIONS,不仅浪费带宽,还影响性能有木有。

在调试这个问题的过程中,俺也更深入地去理解了一些问题,比如OPTIONS请求的必要性。是这样的,对于跨域请求,限制主要是在浏览器端,当我们POST一个跨域请求时,即便返回不符合同源策略的错误,服务器端也已经修改了数据,这就有点诡异了,明明前端看到的是请求失败,实际上数据修改可能已经成功了,多分裂啊,OPTIONS请求就先进行探测,如果不允许跨域,那浏览器干脆就不发post请求了,免得出现这种分裂现象,OPTIONS请求的意义也就在于此,而且据我了解,这个请求无法禁止。另外一个问题是,前端如果设置代理的话,CORS问题就解决了,那浏览器的同源策略意义何在?前端设置代理,是不是突破了浏览器的限制,使得同源策略形同虚设并违背安全要求呢?其实浏览器的限制是有必要的,要不然我在某个平台上发表一篇文章,里面放个链接,链接到我自己放在某处的Javascript代码,用户点击链接就执行我这个JS代码,会怎样呢,想想就可怕有木有。前端代理的作用就是可以访问跨域数据,因为服务器端并不知道你是否跨域,所以前端代理避开了浏览器的限制,并没有违背服务器端的安全限制,只是使得请求类似于你使用curl命令去请求一样没有受到浏览器某些限制而已。

在搜索的过程中,网上这些资源俺感觉还不错,虽然没能帮助俺解决问题,但多多少少有些启发,感谢他们,在这里放几个链接。

跨域资源共享 CORS 详解

前端常见跨域解决方案(全)

浅谈preflight request

前端跨域请求终极解决方案

cors-access-control-allow-headers-wildcard-being-ignored

发表评论

电子邮件地址不会被公开。