跨域
为什么出现跨域? 出于浏览器的同源策略限制
1. 同源策略
同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。
源
源 = 协议 + 域名 + 端口号
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
非同源限制
-
无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
-
无法接触非同源网页的 DOM
-
无法向非同源地址发送 AJAX 请求
有三个标签允许跨域加载资源
<img src=xxx> // 用于打点统计,统计网站可能是其他域
<link href=xxx> // link script可以使用CDN,CDN的也是其他域
<script src=xxx> // script可以用于JSONP
注意事项:
- 所有跨域请求都必须经过信息提供方允许
- 如果未经允许即可获取,那是浏览器同源策略出现漏洞
2. 解决方法:CORS
- 突破浏览器限制的一个方法
- CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写
- 代码 在后台设置:Access-Control-Allow-Origin
response.setHeader('Access-Control-Allow-Origin','http://XXX.com:9999')
//响应头 Response Headers
header('Content-Type: text/html;charset=utf-8');
header('Access-Control-Allow-Origin:http://localhost:8080'); // *代表允许任何网址请求
header('Access-Control-Allow-Methods:POST,GET'); // 允许请求的类型
header('Access-Control-Allow-Credentials: true'); // 设置是否允许发送 cookies
header('Access-Control-Allow-Headers: Content-Type,Origin,Refer'); // 允许自定义请求头的字段
request.headers['refer'] //获取请求的网站
- 优势:
- 在服务端进行控制是否允许跨域,可自定义规则
- 支持各种请求方式
- 问题:
- 不兼容IE
- options
- 对于非简单请求,浏览器会自动先发送一个
options请求,如果发现服务器支持该请求,则会将真正的请求发送到后端,反之,如果浏览器发现服务端并不支持该请求,则会在控制台抛出错误。
3. 解决方法:JSONP
- IE时代的妥协
- 跨域资源嵌入是允许的,但是浏览器限制了Javascript不能与加载的内容进行交互,如嵌入的
<script>、<img>、<link>、<iframe>等。 - 原理
-
JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。
-
JSONP实现跨域请求的原理简单的说,就是动态创建
-
JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。
// 动态创建<script>标签,设置其src,回调函数在src中设置:
var script = document.createElement("script");
script.src = "https://xxx/yy?name=ccc&callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
// 在页面中,返回的JSON作为参数传入回调函数中,我们通过回调函数来来操作数据。
function handleResponse(response){
// 对response数据进行操作代码
}
- 示例演示
背景: frank.com想访问qq.com
过程:
qq.com后端:
if(path === '/friends.js'){
const string = fs.readFileSync('./public/friends.js').toString()
const data = fs.readFileSync('./public/friends.json').toString()
const string2 = string.replace("{{data}}",data)
response.write(string2)
response.end()
}
friends.js
window.xxx= {{data}}
请求成功后,friends.js会执行,数据会放到window.xxx上。
frank前端
window.xxx= (data)=>{
console.log(data)
}
// 创建script标签
const script = document.createElement('script')
script.src=`http://qq.com:8888/friends.js`
// script放到body里
document.body.appendChild(script)
//监听script的onload事件
script.onload = ()=>{
console.log(window.xxx)
}
- 由此引出的问题: 这是jsonp所有网站都可以引用了 !!!
- 解决方法:用refer检查, 可以定向分享
if (request.headers['referer'].indexOf('http://frank.com:9999') === 0){
// 做上述JSONP的后端处理
} else {
response.statusCode = 404
response.end()
}
- JSONP总结
-
JSONP是通过 script 标签加载数据的方式去获取数据当做 JS 代码来执行
-
提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP 需要对应接口的后端的配合才能实现。
- 封装JSONP
- 封装
function jsonp(setting){
setting.data = setting.data || {} // 查询参数
setting.key = setting.key||'callback' // callback名字
setting.callback = setting.callback||function(){} // callback方法
setting.data[setting.key] = '__onGetData__' // callback加入到查询参数中
window.__onGetData__ = function(data){ //
setting.callback (data); // 执行callback方法
}
var script = document.createElement('script')
var query = []
for(var key in setting.data){ // 将查询参数拼接
query.push( key + '='+ encodeURIComponent(setting.data[key]) )
}
script.src = setting.url + '?' + query.join('&') //拼接查询参数和url
document.head.appendChild(script)
document.head.removeChild(script)
}
jsonp({
url: 'http://api.jirengu.com/weather.php',
callback: function(ret){
console.log(ret)
}
})
jsonp({
url: 'http://photo.sina.cn/aj/index',
key: 'jsoncallback',
data: {
page: 1,
cate: 'recommend'
},
callback: function(ret){
console.log(ret)
}
})
- 封装
function jsonp(url,data,callbackName){
return new Promise((resolve,reject)=>{
// 触发callback,触发后删除js标签和绑定在window上的callback
window[callbackName] =(data)=>{
delete window[callbackName]
document.body.removeChild(script)
if(data){
resolve(data)
} else{
reject('没有数据返回')
}
}
// 初始化url
let dataString = url.indexOf('?') === -1 ?"?" :""
url+=`${dataString}${callback}=${callbackName}`
if(data){
for(let key in data){
url+=`&${key}=${data[key]}`
}
}
//创建script标签
const script = document.createElement('script')
script.src = url
// 加载后将script移除
script.onload=()=>{
// script.remove()
}
// js加载异常的情况
script.onerror=()=>{
document.body.remove(script)
reject('js加载失败')
}
// 添加js节点到document上时,开始请求
document.body.appendChild(script)
})
}
jsonp('url',{
a:1,
b:'zzz'
},
callbackName)
.then(res=>{
console.log(res)
})
.catch(err=>{
console.log(err)
})
所以JSONP是什么?
- 是什么:
- 创建一个script标签,去请求另一个网站的js。
- js里会夹带一些数据,这些数据会在我的网站中调用一个全局函数(callback)
- 优点
- 兼容IE
- 可以跨域
- 缺点
- 由于是script标签,所以补不到ajax那么精确的状态(比如状态码,头),他只知道成功和失败。
- 由于是script标签,只能发get请求,不支持post。