跨域问题
Reference Resource
目录
跨域why
结论:浏览器的安全机制,不许跨域, 一是针对接口的请求,二是针对Dom的查询
跨域what
跨域how
JSONP
jsonp(JSON with Padding)只能发GET请求,在HTML标签比如<script>、<img src>这样的获取资源的标签是没有跨域限制的
jsonp的核心是动态添加<script>标签调用服务器提供的js脚本(后缀.json)
ajax的核心是通过xmlHttpRequest获取非本页内容
CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)
CORS 实现的原理:使用额外的 HTTP 头来告诉浏览器,让运行在某一个 origin 上的 Web 应用允许访问来自不同源服务器上的指定的资源。
简单来说,浏览器一旦发现请求是一个跨域请求,首先会判断请求的类型,
如果是简单请求,会在请求头中增加一个 Origin:
字段,表示这次请求是来自哪一个源。而服务器接受到请求后,会返回一个响应,响应头中会包含一个叫 Access-Control-Allow-Origin:
的字段,它的值要么包含由 Origin 首部字段所指明的域名,要么是一个 "*",表示接受任意域名的请求。如果响应头中没有这个字段,就说明当前源不在服务器的许可范围内,浏览器就会报错:
GET /cors HTTP/1.1
Origin: https://xxx.xx
Accept-Language: en-US
Connection: keep-alive
如果是非简单请求,会在正式通信之前,发送一个预检请求(preflight),目的在于询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段,只有得到肯定答复,浏览器才会发出正式的请求,否则就报错。日常的开发中,使用 OPTION 方法发起的请求,就是一个预检请求:
OPTIONS /cors HTTP/1.1
Origin: http://xxx.xx
Access-Control-Request-Method: PUT
Accept-Language: en-US
那么到底哪些是简单请求,哪些是非简单请求 ?
请求类型
不会触发 CORS 预检的,就是简单请求。
使用以下方法之一:GET, HEAD, POST,
并且 Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
Websocket
Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。什么是全双工通信 ?简单来说,就是在建立连接之后,server 与 client 都能主动向对方发送或接收数据。
需要注意的是,Websocket 属于长连接,在一个页面建立多个 Websocket 连接可能会导致性能问题。
Nginx 反向代理
我们知道同源策略限制的是:浏览器向服务器发送跨域请求需要遵循的标准,
那如果是服务器向服务器发送跨域请求就不受浏览器的同源策略限制。
利用这个思路,我们就可以搭建一个代理服务器,接受客户端请求,然后将请求转发给服务器,拿到响应后,再将响应转发给客户端:
这就是 Nginx 反向代理的原理,只需要简单配置就可以实现跨域:
# nginx.config
# ...
server {
listen 80;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用 webpack-dev-server 等中间件代理接口访问 nignx 时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
# ...
}
}
Node 中间件代理
实现的原理和我们前文提到的代理服务器原理如出一辙,只不过这里使用了 Node 中间件做为代理。需要注意的是,浏览器向代理服务器请求时仍然遵循同源策略,别忘了在 Node 层通过 CORS 做跨域处理:
const https = require('https')
// 接受客户端请求
const sever = https.createServer((req, res) => {
...
const { method, headers } = req
// 设置 CORS 允许跨域
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': 'Content-Type',
...
})
// 请求服务器
const proxy = https.request({ host: 'xxx', method, headers, ...}, response => {
let body = ''
response.on('data', chunk => { body = body + chunk })
response.on('end', () => {
// 响应结果转发给客户端
res.end(body)
})
})
// 结束请求
proxy.end()
})
跨域方案总结
- CORS 支持所有的 HTTP 请求,目前主流的跨域解决方案😁
- JSONP 只支持 GET 请求,但是可以兼容老式浏览器
- Node 中间件和 Nginx 反向代理都是利用了服务器对服务器没有同源策略限制
- Websocket 也是一种跨域的解决方案
- PostMessage 可以实现跨文档通信,更多地用于窗口通信
- document.domain, window.name, location.hash 逐渐淡出历史舞台,作为替代 PostMessage 是一种不错的方案