07-了解跨域

了解跨域

一、什么是跨域?

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,它是基于浏览器的同源策略的,所谓同源,就是指请求和响应双方的协议、域名、端口号三者要相同,只要有其中一个不同,都会发生跨域限制,以下是几种跨域的情况:

当前页面url被请求页面的url是否跨域原因
http://www.test.comhttp://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.htmlindex.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.jsdevServer设置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再更新文章~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值