【秒杀】前端网络-CORS
本文最后更新于:2024年3月31日 晚上
前言
上一节介绍了前端网络的基础用法,已经秒杀了fetch与xhr用法,但是实际在前端发送这些请求的时候,难免会遇到一些莫名其妙的报错,在别人网站正常请求的服务器地址,在你的网站里面就不行了,我用APIfox,postman或者vscode的rest client发送请求就一切正常,这是怎么回事?
其实这是来自于浏览器的安全策略“跨源资源共享”
浏览器限制
跨源资源共享(CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。
例如a.com发送一个POST请求到服务器,是正常的,而b.com发送却失败,这就是因为服务器有着一个“Access-Control-Allow-Origin”响应头,检测到b.com不在允许请求的范围内,故浏览器拦截了这一请求。但是有个例外,上述提到的APIFox,postman这些工具能正常请求的原因是他是从服务端进行请求的,不是浏览器的环境,自然没有这样的限制,你可以理解为浏览器是高中的学生,他不允许你使用手机,而postman是高中老师,自然不受校规对学生用手机的限制。
何为跨源
以下情况,都会引起跨源的情况
- 端口不同:https://a.com和https://a.com:81
- 主机不同:https://a.com和https://b.com和https://a.a.com
- 协议不同:http://a.com和https://a.com
检查两个域之间是否产生跨源问题,可以前往 https://httptoolkit.com/will-it-cors/ 进行测试
浏览器产生CORS的场景
- XHR或fetch发起的跨源 HTTP 请求。
- Web 字体(CSS 中通过 @font-face 使用跨源字体资源)
- WebGL 贴图。
- 使用 drawImage() 将图片或视频画面绘制到 canvas。
- 来自图像的 CSS 图形。
本节重点关注http请求的部分,在http中,浏览器将 CORS 请求分成两类:简单请求(Simple request)和非简单请求(Not-so-simple request)
简单请求
如果以下三种类型都满足,则视为简单请求的类型
请求方法为以下三种之一
- HEAD
- GET
- POST
HTTP 的头信息不超出以下字段
- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
Content-Type 仅限于以下三种之一
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器。
请求中没有使用 ReadableStream 对象。
满足简单请求的时候,浏览器会向服务器原 封不
动地发送请求
浏览器封装后的http请求类似下文
1 |
|
随后获取服务器返回的 Access-Control-Allow-Origin
响应头,确定是否进行跨源异常处理,例如上面的请求,服务器返回了如下请求头:
可以看到,Access-Control-Allow-Origin
为 *
,说明允许所有域名跨源请求这个API。
浏览器看到了觉得没问题,那就通过。
非简单请求
如果简单请求条件的其中一条不满足,则视为非简单请求。
在进行非简单请求时,浏览器会按顺序一步步执行,比如这里我们做了一个GET请求,并且携带了一个自定义的 Token
请求头
由于 Token
请求头超出了简单请求的范围,浏览器会进行下面的一系列操作。
- 发出
OPTIONS
的请求进行预检,在浏览器开发者工具网络面板里面可以看到
- 预先检测服务器是否允许此种请求头,请求方法,发送请求的源站点,如果发出请求的方法包含在
Access-Control-Allow-Headers
,Access-Control-Allow-Methods
,Access-Control-Allow-Origin
响应内,则浏览器准许放行。
- 正式发送请求。在预检完毕后,浏览器则最终会将原来的请求原xx动地发送到服务器,此时与前面简单请求的流程一致,最终拿到http请求结果。
这样的请求就类似小区门口的保安,如果车牌,车主和手机号(请求头,方法,域)都登记在小区(服务器)内,保安检查(预检)觉得没问题,就准许放行,此时我们就可以正式访问,进到小区里面。
前端背锅
整套流程下来发现,其实是浏览器限制了我们这么操作,但是归根结底不是前端的问题,实际上CORS是服务器的锅。在上一节提到了,客户端和服务器发送请求是双方协商好的,不是因为前端有GET后端才能GET,也不是因为后端有 Accept-Content
这个头,我就拿这个头传数据。
前端请求,也只是遵循了写后端的程序员写的规则而已
后端返回,也只是听从了前端程序员的建议而已
他俩其实谁也不怪谁,如果真正发生CORS的错误,需要后端改动响应头的内容。而前端能做到的,就是什么也不做,因为问题的根源在浏览器本身,你当然可以通过修改浏览器配置使其不再检测,但是成千上万的用户,谁也不知道谁有没有这个限制。
既然浏览器和前端没法变通,那么后端的人员就得精明一点了,老老实实按照规范把这些响应头加回去。
前端网络,但是后端
虽说这里讲的是前端网络,不过可以稍微点一下后端的内容
因为后端百花齐放,千奇百怪,所有的语言,框架从a-Z,0-9可以给你列出几千万种。
这里就以nodejs的express为例,讲一下如何解决前端跟你提的CORS的问题
既然我们知道了这个是由响应头不对劲引起的,那么就可以再每次请求的时候给浏览器一个正确的回复
注意:以下是错误写法
1 |
|
如果在简单请求可能还不会出事,但是如果是非简单请求,打印网络日志,会发现浏览器先发送了OPTIONS请求后再发送GET请求,由于这里写的是 app.get
,意味着只有在 GET
请求的时候才会正确处理发送响应头的函数,所以OPTIONS就已经被拦截了,不会正式发送 GET
请求。
正确写法如下:
1 |
|
对所有请求都做如上返回响应头的处理,这样能保证浏览器预检能够通过。
总结
其实这不是你的问题,是浏览器限制和后端不正确的响应共同导致的。对于前端来说只能提供一个思路,告诉前端人员这个错误造成的原因,要真修起来只能干瞪眼。前端能做的就是鞭策后端工程师赶紧修了,你的责任就是等他修好。
到这里,关于在前端进行网络请求的内容就已经算是入门+1了,往后仍有更长的路要走,本章仅仅对HTTP的CORS进行讲解,以后还会遇到像上传文件,下载文件,跑通接口,跨域,认证,jwt token,session,登录注册,SSE(服务器主动发送事件),Websocket(服务器客户端双向通信)等等一系列更复杂的实战挑战,一切的前提,是学会HTTP,解决CORS的问题,成功在前端跑通服务器。(来自上一篇文章)