- 请问怎么跨域?
- 注意:无论是CORS还是JSONP都是要双方主动沟通,也就是对方必须要写一个头文件或者添加一个JS文件,没有这些还是不能访问,不能去抢数据。
解法一:CORS
- 问题根源
1.浏览器默认不同源之间不能相互访问数据
2.但qq.com和lzy.com其实都是我的网站
3.我就是想要两个网站相互访问,浏览器为什么阻止 - 解决问题:用CORS
1.浏览器说,如果要共享数据,需要提前声明
2.浏览器说,qq.com在响应头里写lzy.com可以访问
3.具体语法:Access-Control-Allow-Origin:htttp://foo.example
4.详情都在文档里:MDN文档 - 具体做法:
1.在qq-com目录中,server.js文件里的friends路径中设置一个响应头
response.setHeader("Access-Control-Allow-Origin", "http://lzy.com:9999");
2.意思是friends.json允许这个网站端口访问
- 允许多个网站访问
request.headers['referer']
referer可以读取是哪个网站访问json文件,然后直接通过referer把访问的地址写到允许的地方即可 - 注意:CORS分为简单请求和复杂请求,具体看文档
- IE 6789都不支持CORS,只有采用JSONP
解法二:JSONP
1.定义
- JSONP和JSON没有任何关系
在发明的时候是为了请求JSON数据,但其实不限于访问JSON数据,还有xml等都可以,因此和JSON没有特别严谨的关系 - 问:没有CORS怎么跨域?
答:虽然不能访问 .json文件,但我们可以访问任意网站的 .js文件 - 问:JS又不是数据
答:把数据写到JS文件里面就行了
2.步骤
- lzy.com 访问 qq.com
- qq.com 将数据(data)写到 /friends.js文件中
1.不用一开始就将数据写进去,先写一个占位符{{data}}
,然后后台server.js
将其替换(replace)掉即可。
2.JS文件内容:window.xxx = {{data}}
——因为替换过来的JSON文件字符串不符合JS文件的语法
——因此采用赋值就符合JS文件的语法了
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()
}
- lzy.com 用 script 标签引用 /friends.js
1.使用JS动态引用的
2.每请求一次,就会产生一个 script标签,多次以后就会导致页面很臃肿
——优化:请求玩之后就删掉script标签
const script = document.createElement('script')
script.src = 'http://qq.com:8888/friends.js'
script.onload=()=>{//监听script标签的onload事件
console.log(window.xxx)
script.remove()
}
document.body.appendChild(script)
- 获取数据:
window.xxx
因为请求过来的JS文件会执行,也就是把数据写到了 window.xxx 上面 - 关于获取的JS文件
1.window.xxx
是赋值:获取JS文件并执行,相当于是赋值
2.window.xxx
是函数:执行JS文件相当于是调用函数,因此要提前定义
//friends.js中是赋值
window.xxx = {{data}}
//沟通一下:在允许源这边就可以直接赋值使用
script.onload=()=>{
console.log(window.xxx)
}
//friends.js是函数
window.xxx({{data}})
//允许源:这边就先定义一个函数准备好
window.xxx = (data)=>{
console.log(data)
}
- window.xxx 就是一个回调啊!!!
定义了函数window.xxx
,却不调用它,等qq.com的一个JS脚本来调用,其结果就是调用的参数(这就是跨域名的回调)
3.referer
- JSONP的话所有网站都能访问该JS文件里的数据内容了
CORS可以指定谁能访问,但JSONP不能指定 - 可以做referer检查
1.request.headers['referer']
2.如果检查到地址不是我们所允许访问的,就不让其访问(做个字符串匹配)
3.不是就给一个404
else if (path === "/friends.js") {
if(request.headers['referer'].indexOf("http://lzy.com:9999")===0){
response.statusCode = 200;
response.setHeader("Content-Type", "text/json;charset=utf-8");
const string = fs.readFileSync("./public/friends.js").toString(); //变成字符串
const data = fs.readFileSync("./public/friends.json").toString();
const string2 = string.replace("{{data}}", data); //替换JS文件中的内容为真正的数据
response.write(string2);
response.end();
}else{
response.statusCode = 404
response.end()
}
- 但是一旦给了权限的 lzy.com 被攻陷了,那么 qq.com 也会很危险,还要再加强防御
4.优化
- 函数window.xxx不写死,自动生成(random)
1.这样就可以接收多个JS文件,且不会重名
2.但访问的网站中JS并不知道我们的xxx叫什么,则使用查询参数传给/friends.js
//名字随便怎么取,越复杂,越不会重名
const random = 'frankJSONPCallbackName'+ Math.random()
window[random] = (data)=>{
console.log(data)
}
const script = document.createElement('script')
script.src = `http://.../friends.js?functionName=${random}`
document.body.appendChild(script)
- 访问后台(serve.js)中获取到查询字符串
//friends.js
window['{{xxx}}']({{data}})
//serve.js
const string2 = string.replace("{{data}}", data).replace('{{xxx}}',query.functionName)
- 这样随机的函数名就不会和别人冲突
5.封装(提高课)
- 封装成
jsonp('url').then(f1,f2)
给一个url,成功就调f1,失败就调f2 - 步骤:
1.先定义一个函数jsonp
2.创建一个Promise对象
3.将用户传入的url当做jsonp函数的参数
4.成功拿到data调用f1,失败则调用f2 - JSONP比AJAX有一个天然的弱点,拿不到状态码,只知道成功或者失败
- 封装完成后,只需要一句话即可请求JSONP
jsonp('http://qq.com:8888/friends.js')
.then((data)=>{
console.log(data)//成功后调用函数console.log
})
6.functionName
- JSONP约定,functionName统一叫callback
- 后端的server.js中也统一叫callback,这是约定
script.src = `${url}?callback=${random}`