定义
所谓跨域,就是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。而我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
有关同源策略,可以阅读阮一峰老师的文章,讲的非常详细:
www.ruanyifeng.com/blog/2016/0…
总结的来说,由于浏览器安全限制,数据是不可以直接跨域请求的,包括不同的根域名、二级域名、或不同的端口,除非目标域名授权你可以访问。想要解决同源策略实现传输,就需要跨域。
网站url由 协议 + 域名 + 端口 + 路径组成,也可能包含查询参数和锚点
这里我借用了方应航老师画的图:
接下来我们就盘点一下跨域的解决方案:
1、 jsonp
2、跨域资源共享(CORS)
3、postMessage
4、window.name + iframe
5、location.hash + iframe
6、 document.domain + iframe
7、 WebSocket协议跨域
8、 nginx代理跨域
1、JSONP
原理
虽然有同源策略的限制,但script却可以随意引用外部的链接。JSONP就是利用了这一特性,通过创建一个script标签,其src指向非同源的url,来实现跨域。
方法
1、写一个jsonp函数,返回一个promise对象
2、创建一个script标签
2、script.src = url
3、在document中插入script标签
4、在全局挂载一个show函数,执行resolve,并且删除script标签
原生代码实现:
function jsonp(url){
return new Promise((resolve,reject)=>{
let script = document.createElement('script')
script.src = url
document.body.appendChild(script)
window.show = function(data){
resolve(data)
document.body.removeChild(script)
}
})
}
jsonp(url).then((data)=>{
console.log(data)
})
不过JSONP也有比较明显的缺点,只能发送GET请求,而且比较缺乏安全性,容易收到xss攻击。
2、CORS(跨域资源共享)
这种方法也是比较常用的一种。CORS全称是Cross-Origin Resource Sharing,也就是跨域资源共享。
通俗一点来讲,就是如果A客户端想要跨域去给不同域名的B服务器发送请求,就提前和B服务器说好,然后B的后端代码就加上一段允许A发送请求并响应A的代码,达到效果。
加入的后端代码如下:(用到了express)
let express = require('express')
let app = express()
let whiteLists = ['//你同意请求的页面']
app.use(function(req,res)=>{
let origin =req.headers.origin
if(whiteLists.includes(origin)){
res.setHeader('Access-Control-Allow-Origin', origin)
}
})
res.setHeader('Access-Control-Allow-Origin', '//你同意请求并响应的页面域名')
加入这句就表明这个网站加入白名单,可以响应他的请求。
要注意,默认支持的请求方式是get、post、head,如果要使用put这类复杂请求,同样需要在后端setheader
res.setHeader('Access-Control-Allow-Methods','put')
常见的setHeader总结:
res.setHeader('Access-Control-Allow-Origin',origin)
//允许哪个地址向我发送请求
res.SetHeader('Access-Control-Allow-Headers','name')
//允许哪个请求头访问我
res.setHeader('Access-Control-Aloow-Methods','PUT')
//允许哪个方法访问我
res.setHeader('Access-Control-Allow-Credentials',ture)
//允许携带cookie
res.setHeader('Access-COntrol-Max-Age',1)
//允许存活的时间(单位时间为5s)
res.setHeader('Access-Control-Expose-Headers','name')
//允许返回的头
3、postMessage
postMessage是HTML5中的一个api,可以实现跨域操作的window属性,它可以解决以下问题:
- 页面与其新打开的窗口的数据传递
- 多窗口之间的数据传递
- 页面与嵌套的iframe消息传递
总的来说,postMessage()可以实现跨文本档、多窗口、跨域消息传递。
具体步骤:
1、在html中写一个iframe
标签,src属性为需要通信的窗口的url
2、在iframe
标签设置一个onload监听事件函数load
3、load函数中对iframe的contentWindow使用postMessage('内容','url')
4、在接收的网页中写window.onMessage = function(e){console.log(e.data)}
接收发送内容
5、如果需要返回内容,则继续在onMessage中加上e.source.postMessaqg('内容',e.origin)
实现代码:
http://localhost:8080/x.html
页面向http://localhost:8081/y.html
传递'request',然后后者传回'response'。
x.html(http://localhost:8080/x.html
)
<iframe src="http://localhost:8081/y.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
//内嵌在http://localhost:8080/x.html
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('request', 'http://localhost:8081') //发送数据
window.onmessage = function(e) { //接受返回数据
console.log(e.data) //'response'
}
}
</script>
y.html(http://localhost:8081/y.html
)
window.onmessage = function(e) {
console.log(e.data) //'request'
e.source.postMessage('response', e.origin)
}
4、window.name
window.name
的特性:name值在加载完其他页面后仍然能够存在,可支持2MB。我们可以利用这一特性实现跨域。
实现步骤:
1、创建一个iframe
标签,src属性为需要发送内容的url
2、在iframe上设置一个onload监听事件函数load
3、设置onload事件触发两次,第一次在load函数中修改iframe的src,改为同域下的另外一个网页,可为空白页,并把数据保存在window.name
4、第二次触发onload事件,读取同域window.name
中的数据
实现代码:
x.html与y.html同域,向z.html发送内容。其中b.html为中间代理页,与a.html同域,内容为空。
<iframe src="http://localhost:8080/z.html" frameborder="0" id="iframe" onload="load()"></iframe>
<script>
let first = true
function load(){
if(first){
let iframe = docuement.getElementById('iframe')
iframe.src = 'http://localhost:8080/y.html'
fitst = false
}else{
console.log(iframe.contentWindow.name)
}
}
</script>
5、location.hash + iframe
思路:x给z传一个hash值,z收到hash值后,把hash值传递给y,y将结果放到x的hash中。xyz三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
实现代码:
x.html
<iframe src="http://localhost.8081/z.html#text1" frameborder="0"></iframe>
<script>
window.onhashchange = function(){
console.log(location.hash)
}
</script>
z.html
<script>
console.log(location.hash)
let value - location.hash
let iframe = document.createElement('iframe')
iframe.src = `http://localhost:8080/y.html#Ireceived${value}`
document.body.appendChild(iframe)
</script>
y.html
<script>
window.parent.parent.location.hash = location.hash
</script>
6、document.domain + iframe
适用场景:二级域名相同的两个网页进行发送请求,如a.test.com
和b.test.com
。
实现原理:在两个页面中的js加入document.domain ='test.com'
,也就是document.domain = '相同的二级域名'
实现代码: a.html
The a.html
<iframe src="http://b.test.com:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
<script>
document.domain = 'test.com'
function load() {
console.log(frame.contentWindow.a);
}
</script>
b.html
The b.html
<script>
document.domain = 'test.com'
let a = 100;
</script>
这样a就可以获取b的变量a了
7、websocket
Websocket是一个现成的api,想要使用的话,直接new就可以了
let socket = new Websocket()
但是这个api兼容性差,所以我们一般使用封装零零websocket接口的socket.io
这里展示原生webocket的实现代码:
客户端:
<script>
let socket = new WebSocket('ws://localhost:8080');
socket.onopen = function () {
socket.send('request');//向服务器发送数据
}
socket.onmessage = function (e) {
console.log(e.data);//接收服务器返回的数据
}
</script>
服务端:
let express = require('express');
let app = express();
let WebSocket = require('ws');//需要安装ws
let wss = new WebSocket.Server({port:8080});
wss.on('connection',function(ws) {
ws.on('message', function (e) {
console.log(e.data);
ws.send('response')
});
})
8、 nginx代理跨域
Nginx是一款轻量级的Web服务器、反向代理服务器,我们可以搭建一个nginx服务器用于中转,从而实现转发请求。
实现思路:
1、在nginx配置一个代理服务器作为跳板
2、反向代理访问domain2接口,实现跨域
实现步骤:
1、在官网下载nginx
2、在nginx目录下修改nignx.conf
3、启动nginx
修改nignx.conf的代码如下:
server {
listen 81;
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;
}
}