跨域
同源策略
什么是源? 源(Origin)是由 URL 中协议、主机名(域名 domain)以及端口共同组成的部分。顾名思义,满足协议,域名,端口号都相同的 url 才满足同源策略
什么时候会不满足同源策略呢? 当一个源访问另一个源的资源时,就会产生跨源,也就是我们平时说的跨域。
同源策略的作用是什么?同源策略就是用来限制其中一些跨源访问的,包括访问 iframe 中的页面、其他页面的 cookie 访问以及发送 AJAX 请求 最常见的跨域场景是域名不同
同源策略有什么影响? 同源策略在保障安全的同时也带来了不少问题,比如 iframe 中的子页面与父页面无法通信,浏览器与其他服务端无法交互数据等。
请求跨域
为了解决上述问题, 跨域方案应运而生。主要分为两个方向,请求跨域和页面跨域。
在了解请求跨域方案之前,先看看请求的分类,请求分为简单请求和非简单请求
简单请求
符合下面 2 个特征。
-
请求方法为 GET、POST、HEAD。
-
请求头只能使用下面的字段:
Accept(浏览器能够接受的响应内容类型)
Accept-Language(浏览器能够接受的自然语言列表)
Content-Type (请求对应的类型,只限于 text/plain、multipart/form-data、application/x-www-form-urlencoded)
Content-Language(浏览器希望采用的自然语言)
Save-Data(浏览器是否希望减少数据传输量)
看到这里差点以为只有 GET、POST、HEAD 三种方法可以用跨域资源共享的方式,然而并不是, 非简单请求有自己的处理方式
非简单请求
不满足简单请求特征的即为非简单请求
非简单请求会先发送一个类型为 option 的预请求(Preflight)
跨域资源共享
浏览器部分
会添加一个请求头部字段 Access-Control-Request-Method, 以此来定义本次请求的方法,差点以为 restful 风格的 http 请求用不了这个方法。。。
因为在 Access-Control-Request-Method 添加了不属于简单请求的头部字段, 所以浏览器在请求头部添加了 Access-Control-Request-Headers 字段,值为 authorization(跨域请求添加的请求头部字段)。
以上是浏览器客户端对请求头的操作,服务器收到预检请求后,会做什么操作呢?
服务器部分
服务器在收到预检请求后,除了在响应头部分添加 Access-Control-Allow-Origin 外, 还会添加 Access-Control-Allow-Methods, 告诉客户端自己允许的请求方法,并返回 204 状态码。
这就结束了吗? ............ 并没有
服务端还根据浏览器的 Access-Control-Request-Headers 字段 回应了一个 Access-Control-Allow-Headers 字段,来告诉浏览器服务端允许的请求头部字段
浏览器得到预检请求响应的头部字段之后,会判断当前请求服务端是否在服务端许可范围之内, 如果在则继续发送跨域请求,反之则直接报错。 THE END...
JSONP
最早接触 jsonp 是在 ajax 里,只知道 ajax 的这个属性可以配置是否跨域 原来 JSONP 的全称是 JSON with Padding, 实现方式是把 JSON 数填充到一个回调函数中。 这种比较 hack (???啥意思)的方式,依赖的是 script 标签跨域引用 js 文件不会受到浏览器同源策略的限制。
实现方式
- 声明一个函数
function fn(result) {
console.log(result)
}
- 把函数名作为变量名写入 URL 中
var url = 'http://www.b.com?callback=fn¶ms=...';
- 创建 script 标签, 把 url 赋值给 script 的 src (jsonp 跨域的原理就是依赖 src 属性属性可以跨域)
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = url;
document.body.appendChild(script);
- 服务器接收到请求后,解析 url 并进行相应的处理,将得到的结果以回调函数的形式返回给浏览器
fn({
list: [],
...
})
- 浏览器收到请求返回的 js 脚本之后,会立即执行文件内容,即在控制台打印传入的数据内容
JSONP 的缺点
-
只能发送 GET 请求,限制了参数大小和类型;
-
请求过程无法终止,导致弱网络下处理超时请求比较麻烦;
-
无法捕获服务端返回的异常信息。
WebSocket
定义 HTML5 规范提出的一个应用层的全双工协议,适用于浏览器与服务器进行 实时通信 场景。 在 a 网站建立一个 websocket 连接,,连接到 b 网站, 然后调用 send 方法向服务器发送消息, 监听 onmessage 事件,得到响应内容
var ws = new WebSocket("ws://b.com");
ws.onopen = function(){
// ws.send(...); 发送 json 对象
}
ws.onmessage = function(e){
// console.log(e.data);
}
代理转发
跨域只存在客户端(默念三遍),所以可以尝试在服务器端解决,也就是配置代理
在服务器端设置的代理为反向代理 在客户端设置的代理成为正向代理
正向代理 使用时必须配置代理服务器的网址,例如 VPN
反向代理 在服务器端设置的代理,例如 nginx
在 webpack-dev-server 中配置代理的代码示例 proxy: { '/api': 'http://localhost:3000' } 在 proxy.js 中配置代码的示例 '/api/': { target: 'http://10.16.32.172:8096', }, 在 nginx 服务器上配置代理 location /api { proxy_pass http://localhost:3000; }
所有前缀为 api 的请求,都会被服务器转发到 target 对象上,对于浏览器而言还是访问当前的网址
页面跨域
除了浏览器请求跨域之外,页面之间也会有跨域需求,例如使用 iframe 时父子页面之间进行通信
1. 改域 document.domain
对于主域名相同,子域名不同的情况,可以通过修改 document.domain 的值来进行跨域。 www.lagou.com/parent.html 和 kaiwu.lagou.com/child.html document.domain = "lagou.com"; 改域之后,父子页面可以进行跨域通信,还可以共享 cookie
2. postMessage
定义 HTML5 推出的新函数,用来实现父子页面之间的通信,而且不管两个页面是否同源 实现代码 a 页面
// https://lagou.com
var child = window.open('https://kaiwu.lagou.com');
child.postMessage('hi', 'https://kaiwu.lagou.com');
b 页面
// https://kaiwu.lagou.com
window.addEventListener('message', function(e) {
console.log(e.data);
},false);
b 页面也可以向 a 页面发送 postMessage 方法, a 页面监听 message 方法。
使用频率低,记住原理就 okk