1. 同源策略
-
源:由URL 的方案(协议),主机(域名)和端口定义。协议+域名+端口
-
同源策略: 为浏览器所规定的,如果JS运行在源A中,便只能获取源A的数据,而不能获取源B的数据,即不允许跨域。
-
同源的判断: 若两个URL的协议、域名、端口号完成一致,才能算为同源。
2. 跨域(cross-origin)
跨域 / 跨源 应该只是翻译上的不同。
- 跨域: 当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。
3. CORS
-
CORS:Cross-Origin Resource Sharing 跨域资源共享
-
当两个不同源的源相互访问数据的时候,需要在被访问的源的响应头里面提前声明可以访问的源,告知浏览器那些源是可以访问自己的。(即服务端来指定哪些主机可以从这个服务端加载资源)
-
具体使用 ACAO
设置响应头的Access-Control-Allow-Origin属性。
- 仅允许来自 foo.example 的访问
Access-Control-Allow-Origin:foo.example
- 允许任意外域访问
Access-Control-Allow-Origin: *
- 仅允许来自 foo.example 的访问
-
缺点:兼容性问题,此方法IE9不支持。
-
3.1 CORS预检
当请求同时满足以下条件时,浏览器会认为它是一个简单请求:
- 使用下列方法之一:
GET
HEAD
POST
Content-Type
的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
- 请求头仅包含安全的字段,常见的安全字段如下
Accept
Accept-Language
Conent-Language
Content-Type
不符合以上条件的请求则为非简单请求。
若为非简单请求,则会经历以下的请求流程:
- 浏览器发送预检请求,询问服务器是否允许
- 服务器允许
- 浏览器发送真实请求
- 服务器完成真实的响应
假设我们要送一个请求如下:
// 需要预检的请求
fetch('http://crossdomain.com/api/user', {
method: 'POST', // post 请求
headers: {
// 设置请求头
a: 1, // 多的字段
b: 2, // 多的字段
'content-type': 'application/json', // 简单请求里不允许的值
},
body: JSON.stringify({ age: 18 }), // 设置请求体
});
第一步:浏览器发送预检请求,询问服务器是否允许
OPTIONS /api/user HTTP/1.1
Host: crossdomain.com
...
Origin: http://my.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: a, b, content-type
预检请求的特点:
- 没有请求体
- 请求方式为
OPTIONS
Access-Control-Request-Method
为后续请求所使用的方法Access-Control-Request-Headers
为后续的真实请求会改动的请求头
第二步:服务器允许
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: a, b, content-type
Access-Control-Max-Age: 86400
响应无响应体,响应头中会有几个字段:
Access-Control-Allow-Origin
:和简单请求一样,表示允许的源Access-Control-Allow-Methods
:表示允许的后续真实的请求方法Access-Control-Allow-Headers
:表示允许改动的请求头Access-Control-Max-Age
:告诉浏览器,多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求了
第三步:浏览器发送真实请求
第四步:服务器完成真实的响应
4. JSONP
-
JSONP:JSON with Padding
-
使用场景
在跨域的时候由于当前浏览器不支持CORS或者其他某些原因不支持CORS,必须使用另外一种方法跨域。于是我们就利用script标签的src请求一个js文件 ,这个js文件会执行一个回调,这个回调里面就有我们的数据。回调名字可以随机形成,以callback参数传递后台,后台以函数执行的形式返回。
-
优点:兼容ie;可以跨域;
-
缺点:由于是使用script标签,所以无法读取到同ajax一样那么精确的状态(状态码、响应头),只能了解请求成功与失败;只能发送get请求,不支持post;
-
-
封装JSONP
以下代码运行在客户端上
function jsonp(url){ return new Promise((resolve, reject)=>{ //使用Promise封装 const callback = 'callbackName' + Math.random() //生成随机的回调函数名,以免重复 window[callback] = data =>{ //该函数挂在window对象上 resolve(data) } const script = document.createElement('script') //创建script元素 script.src = `${url}?callback=${callback}` //src属性,所要请求的脚本,并以查询参数的方式传递回调函数名 script.onload = ()=>{ script.remove() //请求成功后移除标签 } script.onerror = ()=>{ reject() //失败则执行reject } document.body.appendChild(script) //将script标签插入文档中 }) } jsonp('http://xxx.com/yy.js').then(res=>console.log(res),err=>console.log(err))
服务端接收到请求后返回的脚本内容为:
window[callback](data)
此处的callback为客户端请求的查询参数所提供,data则为服务端提供给客户端的数据。
小结:
-
大致流程为
- 客户端提供一个回调函数名
- 客户端动态添加script元素发起跨域请求,并将回调函数名作为查询参数提供
- 服务端返回一串js代码,该代码调用客户端的回调函数,该回调函数接收的实参为所想要传输的数据。
- 客户端跨域请求完毕,回调函数被调用,对数据进行处理。
-
客户端做足了准备,需要服务端来触发调用这一切,当然最重要的还是所返回的数据。
-