文章目录
了解跨域
一、什么是跨域?
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,它是基于浏览器的同源策略的,所谓同源,就是指请求和响应双方的协议、域名、端口号三者要相同,只要有其中一个不同,都会发生跨域限制,以下是几种跨域的情况:
当前页面url | 被请求页面的url | 是否跨域 | 原因 |
---|---|---|---|
http://www.test.com | http://www.test.com/index.html | 否 | 同源(协议、域名、端口号相同) |
http://www.test.com/ | https://www.test.com/index.html | 跨域 | 协议不同(http/https) |
http://www.test.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(test/baidu) |
http://www.test.com/ | http://blog.test.com/ | 跨域 | 子域名不同(www/blog) |
http://www.test.com:8080/ | http://www.test.com:7001/ | 跨域 | 端口号不同(8080/7001) |
跨域的解决方案有以下几种:
- 通过JSONP跨域
- 通过Webpack设置代理跨域
- 跨域资源共享(CORS)
- postMessage跨域
- Websocket协议跨域
- window.name+iframe跨域
- document.domain+iframe跨域
- location.hash+iframe跨域
- nginx代理跨域
- nodejs中间件代理跨域
下面实现几种常用的跨域解决方案。
二、跨域解决方案实践
2.1JSONP
在html中,script
标签不存在跨域请求的限制,而JSONP则是利用这一特点来实现跨域的。但通过JSONP的形式来传,服务端需要接收客户端传来的callback函数并且返回数据,因此通过JSONP跨域需要服务端的支持,而且这种方法只能处理get请求。
首先我们用express弄一个服务端接口:
let express = require('express')
app = express()
app.listen(8001,_=>{
console.log('OK!')
})
app.get('/list',(req,res)=>{
let {
callback = Function.prototype
} = req.query
let data={
code:0,
msg:'hello client'
}
res.send(`${callback}(${JSON.stringify(data)})`)
})
然后前端代码引入jquery的cdn,因为jquery里面给我们提供了JSONP的处理方式,因此ajax请求可以直接用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
$.ajax({
url: 'http://127.0.0.1:8001/list',
method: 'get',
dataType: 'jsonp', //执行的是JSONP的请求
success: (res) => {
console.log(res)
},
})
</script>
</body>
</html>
打开这个html文件,可以看到,我们1904端口也可以请求到8001端口的数据了
2.2通过Webpack设置Proxy代理
首先我们需要在webpack工程下安装axios:
npm run axios
然后我们在src
下新建index.html
和index.js
,html只需要填入html模板即可,js则用axios来发起一个请求:
import axios from 'axios'
axios.get('http://127.0.0.1:8001/list').then(res=>{
console.log(res)
})
由于我们webpack的端口是3036,而请求的服务器是8001,因此发生了跨域
而webpack代理可以帮我们解决,首先更改js文件里面请求的链接,只保留请求的路径:
import axios from 'axios'
axios.get('/list').then(res=>{
console.log(res)
})
然后在webpack.config.js
中devServer
设置proxy
proxy:{
'/':{
target:'http://127.0.0.1:8001',
changeOrigin:true
}
}
这里的target
是你发送请求的服务端地址,changeOrigin
则是设置走代理跨域,然后重启服务,再次发起请求,可以看到服务端成功返回结果:
2.3CORS(跨域资源共享)
这种方式主要还是服务端来做的,只需要对请求头信息进行设置即可,客户端只需要正常发请求就行,这里还是用刚才的服务端文件举例:
//设置跨域
app.all('*', function (req, res, next) {
//设置请求头
//允许所有来源访问
res.header('Access-Control-Allow-Origin', '*')
//用于判断request来自ajax还是传统请求
res.header("Access-Control-Allow-Headers", " Origin, X-Requested-With, Content-Type, Accept");
//允许访问的方式
res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
//内容类型:如果是post请求必须指定这个属性
res.header('Content-Type', 'application/json;charset=utf-8')
next()
})
2.4postMessage跨域
postMessage是Html5新增的一个解决跨域的方法,它可以实现:
- 页面和其打开的新窗口的数据传递
- 多个窗口之间的消息传递
- 页面与嵌套的iframe消息传递
postMessage方法接受两个参数,分别为data和origin:
data
–是需要传输的数据origin
–指定传给的窗口,可以设置为*号表示传递给任意窗口
举个栗子:
首先用express+node创建两个服务端,端口分别设置为8001和8002,然后用app.use()来引用静态资源文件:
//serve1.js
let express = require('express')
app = express()
app.listen(8001, (_) => {
console.log('OK!')
})
app.use(express.static('./'))
//serve2.js
let express = require('express')
app = express()
app.listen(8002, (_) => {
console.log('OK!')
})
app.use(express.static('./'))
然后在postMessage文件夹创建两个html,代码如下:
<!-- A.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<iframe id="iframe" src="http://localhost:8002/postMessage/B.html" frameborder="0" style="display: none;"></iframe>
<script>
iframe.onload = function(){
iframe.contentWindow.postMessage('我是A,over!','http://localhost:8002/postMessage/')
}
//监听B传回来的信息
window.onmessage = function(ev){
console.log(ev.data)
}
</script>
</body>
</html>
<!-- B.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//监听A发送过来的信息
window.onmessage = function(ev){
console.log(ev.data)
//ev.source是A窗口
ev.source.postMessage('这是A发来的消息:'+ev.data+',B页面已经接收到,over!','*')
}
</script>
</body>
</html>
启动两个服务端文件,在浏览器通过serve1服务访问A.html,可以看到控制台成功打印,实现了跨域访问
2.5Websocket跨域
Websocket也是html5的一种新的协议,他实现了浏览器与服务器的全双工同学,同时允许跨域通信,通常用于实时聊天,这里的例子使用socket.io(它对Websocket进行了封装)。
首先写客户端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
<script>
let socket = io('http://localhost:8001')
socket.on('connect', function () {
// 监听服务端消息
socket.on('message', function (msg) {
console.log(msg)
})
// 监听服务端关闭
socket.on('disconnect', function () {
console.log('Server socket has closed.')
})
})
//向服务端发消息
socket.send('我是客户端');
</script>
</body>
</html>
接下来是客户端,这里注意要安装http模块,不能使用express模块:
var http = require('http');
var socket = require('socket.io');
// 启http服务
var server = http.createServer(function(req, res) {
res.end();
});
server.listen('8001');
console.log('OK!');
// 监听socket连接
socket.listen(server).on('connection', function(client) {
// 接收信息
client.on('message', function(msg) {
client.send('我是服务端!我已接收到你的信息');
console.log(msg)
});
// 断开处理
client.on('disconnect', function() {
console.log('服务已关闭');
});
});
启动服务端,打开前端页面,可以看到服务端已经接受到消息并返回了一条信息,前后端实现了跨域通信:
2.6window.name + iframe跨域
这种方式主要是通过iframe的src属性由外域转向本地域,跨域数据由iframe的window.name从外域传递到本地域,利用了iframe本身可以跨域的特性,巧妙地绕过了浏览器的跨域访问限制。
首先我们准备三个文件
-
A.html
–本地域,在8001端口 -
B.html
–外域,在8002端口 -
proxy.html
–代理<!-- A.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <iframe id="iframe" src="http://localhost:8002/NAME/B.html" frameborder="0" style="display: none;" ></iframe> <script> let count = 0 iframe.onload = function () { if (count === 0) { //需要我们先把地址重新指向到同源中,才可以 iframe.src = 'http://localhost:8001/NAME/proxy.html' count++ return } console.log(iframe.contentWindow.name) } </script> </body> </html> <!-- B.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> //服务端需要返回给A的信息都在window.name中存储着 window.name="JJY" </script> </body> </html>
proxy.html只起到代理作用,里面什么东西都不用写,当我们打开A.html后向B.html发送请求,就可以拿到返回的数据了,没有跨域的限制。
三、总结
上面实现了6种比较常见的跨域方式,还有另外4种没有列出来,其中nginx因为我还没有系统地去学习,因此无法给出nginx反向代理实现的例子,日后学习了nginx再更新文章~