在我们使用ajax进行前后端数据交互的时候,经常会遇到一个跨域的报错信息:
Access to XMLHttpRequest at ‘http://localhost:3000/cors‘ from origin ‘null‘ has
been blocked by CORS policy: No ‘Access-Control-Allow-Origin‘ header is present on
the requested resource.
为什么产生跨域?
这是因为浏览器的同源策略。这是浏览器最核心也最基本的安全功能,它阻止一个域上加载的脚本去获取或操作另一个域上的文档属性。(也就是说,受到请求的URL的域必须与当前web页面的域相同),如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
哪些情况会产生跨域?
端口不同
协议不同
域名不同
相同的网址和域名对应的IP不同 //localhost和127.0.0.1会生跨域
总结:端口不同,域名不同,协议不同都会出现跨域
同源策略限制以下几种行为:
Cookie、LocalStorage 和 IndexDB 无法读取
DOM和JS对象无法获得
AJAX 请求不能发送
如何解决解决跨域?
在前端开发中,不可避免要出现跨域问题,下面提供四种方法供参考。
注意:一般出现跨域应该是前后端共同配置解决
方法一:服务器代理
基本原理:服务器之间的请求没有跨域问题。
首先前端请求自己的服务器。 通过第三方中间件cors,来解决前端和自己的服务器之间的跨域问题。
我方服务器发起对目标服务器发送服务器请求。
将网路请求的结果返回给前端
前端代码:
let url =‘http://localhost:3000/cors‘;//请求自己的服务器
$.get(url,(data)=>{
console.log(data);//后端返回的数据
})
server.js:
const cors = require(‘cors‘)
const axios =require(‘axios‘)
const express = require(‘express‘)
const path = require(‘path‘)
const app = express()
// 通过第三方中间件cors 来实现跨域问题
app.use(cors())//如果访问的是目标服务器,那么这里就相当于一个服务器代理
app.get(‘/cors‘,(req,res)=>{
console.log(‘请求到了‘)
let url =‘http://ustbhuangyi.com/music/api/getDiscList?g_tk=1928093487&inCharset=utf-8&outCharset=utf-8¬ice=0&format=json&platform=yqq&hostUin=0&sin=0&ein=29&sortId=5&needNewCode=0&categoryId=10000000&rnd=0.2209329929181545‘
// 直接发起一个服务器请求
axios.get(url)
.then((data)=>{
// console.log(data.data)
res.send(data.data)
})
})
app.listen(3000,()=>{
console.log(‘服务器启动‘)
})
方法二:JSONP方式(前端常用)
核心:script标签的src属性不存在跨域,可以加载任何文件信息。
一般第三方接口可以使用此方法,也是服务器和客户端之间通信的常用方法。全平台浏览器都支持。
比如,需要返回一个淘宝推荐的数据接口
let url = https://suggest.taobao.com
/sug?code=utf-8&q=%E8%A1%A3%E6%9C%8D&_ksTS=1577354356112_350
&callback=taobao&k=1&area=c2c&bucketid=3//前端页面在调用接口时,需要以callback=回调函数名的形式
可以通过script标签实现跨域请求,然后再用json数据并执行回调函数
<script>
function taobao(data){
console.log(data);
}
</script>
这个接口打印出来的数据如下图所示
若项目使用vue写的,那么也可以用jsonp这个模块
api.js
import jsonp from ‘jsonp‘
const getData = () => {
let url = ‘https://suggest.taobao.com/sug?code=utf-8&q=%E8%A1%A3%E6%9C%8D&_ksTS=1577354356112_350‘
// param给后端传递函数名的字段 由后端确定的 不能随便写
return new Promise((resolve, reject) => {
jsonp(url,{param:‘callback‘},(err, data) => {
if(err){
reject(err)
}else {
resolve(data)
}
})
})
}
index.vue
import { getData} from "api/api.js";
getData().then((res)=>{
console.log(res) // 若是请求成功,得到数据
})
这个方法很简单但也有弊端:
jsonp请求方式只能是get。使数据在传递的过程中毫无安全性可言,而且所能传输的数据长度也相当有限。
接口必须符合jsonp格式,否则采用此方法没意义。
方法三:cors设置请求的相应头字段
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
支持所有类型的HTTP请求,但浏览器IE10以下不支持),适合做ajax各种跨域请求。
后端代码:
var express = require(‘express‘);
var app = express();
// 中间件 解决跨域问题
function middleware(req, res, next) {
console.log(‘这里是中间件‘)
// 跨域处理
res.header("Access-Control-Allow-Origin", "*");//设置允许跨域的域名,*代表允许任意域名跨域
res.header("Access-Control-Allow-Headers", "X-Requested-With");//允许的header类型
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");//跨域允许的请求方式
res.header("X-Powered-By", ‘ 3.2.1‘);
res.header("Content-Type", "application/json;charset=utf-8");
next(); // 执行下一个路由
}
//all是路由中指代所有的请求方式
app.all(middleware) //使用全局自定义中间件,放到起始位置
app.get(‘/test‘, function (req, res) {
res.send(req.query);
})
app.listen(3000, function () {
console.log(‘服务器启动‘);
})
用postman进行测试结果如下图所示:
方法四:WebSocket(H5新增特性)
WebSocket是HTML5一种新增的通信协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生websocket有兼容问题,虽然使用起来很简单,但是低版本浏览器使用不了。可以使用第三方库:socket.io 它很好的解决了兼容问题,而且使用方式也只是稍微复杂了一点。
前端代码:
<body>
<input type="text" id=‘msg‘ ><button onclick="send()">send</button>
<script>
// 与服务器实现连接
var socket = io.connect(‘http://127.0.0.1:8081‘);
// 创建一个自定义事件监听 事件名叫hehe 等待服务端触发
socket.on(‘hehe‘,(data)=>{
console.log(‘来自服务端的信息‘,data)
})
function send(){
let msg = document.getElementById(‘msg‘).value
// 客户端触发服务端的自定义事件 事件名叫xixi 传递的数据是input的value值
socket.emit(‘xixi‘,msg)
}
</script>
</body>
server.js:
var express = require(‘express‘)
var app = express()
// 将socket服务器 和express 绑定到一起
var server = require(‘http‘).Server(app);
var io = require(‘socket.io‘)(server);
io.on(‘connection‘,(client)=>{
console.log(‘客户端连接‘)
// 触发客户端的自定义事件 hehe 参数是123
client.emit(‘hehe‘,123)
// 服务端创建一个叫xixi的自定义事件监听 等待前端触发
client.on(‘xixi‘,(data)=>{
console.log(‘来自客户端的消息‘,data)
})
})
server.listen(8081,()=>{
console.log(‘服务器启动‘)
});
方法五: vue-cli的devServer代理配置
如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js 中的 devServer.proxy 选项来配置。vue cli: devServer.proxy
在项目根目录下新建vue.config.js文件
在module.exports内设置devServer来处理代理
module.exports={
devServer:{
proxy:{ //配置代理服务器
// 接口小暗号
‘/hehe‘:{
target:‘http://www.target.com‘, //要转发的目标网址
changeOrigin:true,
pathRewrite:{
"^/hehe":‘‘ //将路径中多余的暗号 删除
}
}
}
}
}
获取接口的网络请求
利用我们之前设置修改的接口小暗号,将请求发送至本地服务器。
此时本地服务器接收的地址是:/hehe/music/api/123 所以不会产生跨域
import axios from ‘../utils/axios‘
let url =‘/hehe/music/api/123‘
axios.get(url)
然后再通过本地服务器转发至目标服务器,在转发之前,本地服务器会把target目标地址:target:‘http://www.target.com‘加至接收地址url前:url =‘http://www.target.com/hehe/music/api/123‘
这时我们发现准备发送的地址和原地址不符,之间多了一个hehe标识符。所以我们需要pathRewrite将/hehe从地址中删去
使用场景
通过 devServer 设置的代理只适合在本地环境使用,即通过代理把请求发送到目标服务器上,如果发布到线上,则会提示 404 路径未找到
小结
还有一些解决跨域的方案,比如nginx代理跨域、 postMessage跨域、document.domain + iframe、location.hash + iframe、 window.name + iframe等等。
没有最好,只有最合适的,结合项目实际场景选择合适的跨域方案。