浏览器的同源策略是为了保护用户的安全,限制了跨域请求。同源策略要求请求的域名、协议和端口必须完全一致,只要有一个不同就会被认为是跨域请求。
本文列举了一些处理跨域请求的解决方案:
- JSONP
- CORS跨域资源共享
- http proxy
- nginx反向代理
- webSocket 协议跨域
- postMessage
- document.domain + iframe
- window.name + iframe
- location.hash + iframe
先说缺点,JSONP只能处理get请求。
script、iframe、img、link 等src属性,不存在跨域请求的限制。利用这个特性可以实现跨域请求
首先在服务器端用node+express模拟一个接口
let express = require("express");
let app = express();
app.listen(8001, (_) => {
console.log("服务器已启动!");
});
app.get("/list", (req, res) => {
// let { callback = Function.prototype } = req.query;
let callback = req.query.callback;
let data = {
code: 200,
messsage: "程序猿小野",
};
res.send(`${callback}(${JSON.stringify(data)})`);
});
1)使用jquery的ajax实现(axios的jsonp方法也可以)
$.ajax({
url: "http://127.0.0.1:8001/list",
method: "get",
dataType: "jsonp", // => 执行的JSONP的请求,不加这一行会报跨域错误,has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
success: (res) => {
console.log(res);
},
});
web本地启动的服务器地址是:http://127.0.0.1:5501,服务器的地址是:http://127.0.0.1:8001/
不加dataType:"jsonp"时会报下面的跨域错误
2)利用 script标签的src属性
function handleResponse(data) {
console.log("接收到的数据:", data);
}
let scriptEle = document.createElement("script");
scriptEle.src = `http://127.0.0.1:8001/list?callback=handleResponse`;
document.head.appendChild(scriptEle);
成功接收到数据:
let express = require("express");
let app = express();
app.listen(8001, (_) => {
console.log("服务器已启动!");
});
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://127.0.0.1:5501");
// res.header("Access-Control-Allow-Origin", "*");
// res.header("Access-Control-Allow-Credentials", "true"); // 后端允许发送Cookie
next();
});
app.get("/list", (req, res) => {
let data = {
code: 200,
messsage: "程序猿小野",
};
res.send(`${JSON.stringify(data)}`);
});
这种情况是目前开发时,前端调试接口解决跨域问题,最常见的处理方法。一般通过webpack webpack-dev-server实现。用vue-cli创建的项目可以在vue.config.js中配置。
index.js
import axios from "axios";
// axios.get("http://127.0.0.1:8001/user/list").then((res) => {
axios.get("/user/list").then((res) => {
console.log(res);
});
没有配置proxy代理时,请求报错:
vue.config.js配置示例:
devServer: {
open: true,
port: 10003,
headers: {
"Access-Control-Allow-Origin": "*",
},
proxy: {
"/": {
target: "http://127.0.0.1:8001/",
secure: false,
changeOrigin: true,
},
},
},
webpack.config.js 配置示例:
let path = require("path");
let HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "production",
entry: "./src/index.js",
output: {
filename: "bundle.min.js",
path: path.resolve(__dirname, "build")
},
devServer: {
port: 3000,
progress: true,
contentBase: "./build",
proxy: {
"/": {
target: "http://127.0.0.1:8001",
changeOrigin: true
},
},
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
})
],
};
配置后请求成功:
nginx 相关的内容比较多,有时间可以另开一个帖子去说。这里举一个我在实际开发过程中遇到的场景。我们的页面调其他前端模块的服务跨域了,在开发中没有,为什么上线后会出现呢。主要是因为开发时前端的服务都在同一个服务器上,不会产生跨域情况。上线部署在不同的服务器,这时候就出现了如下错误:
修改ngnix的相关配置,实际ip地址和对应的服务名已处理。修改后跨域问题解决:
upstream testxxx {
server 127.0.0.1:3000 weight=1;
server 127.0.0.2:4000 weight=1;
}
location /fuwuming{
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://testxxx/fuwuming;
}
查看结果:
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生WebSocket API使用起来不太方便,下面案例借助了socket.io.js的库,,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
前端代码:
Document
user input:
node服务器代码:
// 导入express模块
const express = require('express')
// 引入http创建服务器实例的方法
const {createServer} = require('http')
const {Server} = require('socket.io')
// 创建express的服务器实例
const app = express()
// 创建http服务器实例
const httpServer = createServer(app)
// 创建socket.io的实例
const io = new Server(httpServer,{
// 处理cors,解决跨域问题
cors:{
// origin: "http://127.0.0.1:5500",//需要跨域资源共享的地址
origin: "http://127.0.0.1:5501",//需要跨域资源共享的地址
allowedHeaders: ["my-custom-header"],
credentials: true
}
})
// 监听客户端连接,回调函数会传递本次连接的socket
io.on('connection',(socket) => {
console.log(socket.id)
// 监听到客户端发送的消息
socket.on('sendToServer',(message) => {
console.log(message)
// 向客户端发送消息
socket.emit('sendToClient',{
message:'你好我是服务端,让我们来聊天呀'
})
})
} )
// 调用listen方法,指定端口号并启动web服务器
httpServer.listen(3000,() =>{
console.log('server is running at http://127.0.0.1:3000')
})
效果如下:
MDN关于postMessage的介绍:
测试案列:
先用node启动两个服务器,不同端口,模拟跨域场景。
node serverA.js node serverB.js
let express = require("express");
let app = express();
app.listen(1001, (_) => {
console.log("服务器A已启动!");
});
app.use(express.static("./"));
let express = require("express");
let app = express();
app.listen(1002, (_) => {
console.log("服务器B已启动!");
});
app.use(express.static("./"));
A.html
Document
A 页面
B.html
Document
B 页面
下面几种方案只做了解,实际上几乎用不到。
此方案只能实现:同一个主域,不同子域之间的操作
比如:http://www.domain.com/a.html 和 http://child.domain.com/b.html 属于同一个主域
三个页面:A.html地址为:http://127.0.0.1:1001/A.html
B.html地址为:http://127.0.0.1:002/B.html
Proxy.html地址为:http://127.0.0.1:1002/proxy.html
可以看出B页面和Proxy页面同源,A想访问B的数据就跨域了
B.html的代码,可以看到B.html的window对象上有一个name属性
Document
Document
直接读不允许,控制报跨域错误
将iframe的src属性修改成和B.html同域的Proxy.html地址
Document
在A.html中成功读取到B.html的数据
A和C同源,A和B非同源
A.html
Document
B.html
Document
C.html
Document
如果还有其他方案,欢迎留言补充。
总结一下:2、3、4、5 我个人觉得是必须要掌握的,因为我在实际的项目开发中都使用过。