day-045-forty-five-20230408-JavaScript前端跨域方案
JavaScript前端跨域方案
-
浏览器有一个安全策略: 客户端与服务器的 协议、域名、端口号一致才能访问服务器的数据。
-
客户端与服务器的 协议、域名、端口号 的三项有一项不同,客户端就无法访问服务器的数据。
-
如
- 客户端网址:
http://127.0.0.1:5500/1.html - 服务器网址:
http://127.0.0.1:8888/articleList?date=2021-05-21
- 客户端网址:
-
客户端不能访问服务器的数据
-
-
-
跨域指的是协议与域名与端口号就算有一项或多项不同,也可以让其访问数据。
-
跨域的前提条件: 后台允许 或 后台不做限制。
-
跨域的方式
-
cors 后端设置,要了解的
-
设置白名单–修改白名单,一定要重新启动服务
let safeList = ["http://127.0.0.1:8848","http://127.0.0.1:5500","http://127.0.0.1:5501","http://127.0.0.1:5502", "http://127.0.0.1:3000", "http://127.0.0.1:8080"]; app.use((req, res, next) => { let origin = req.headers.origin || req.headers.referer || ""; origin = origin.replace(/\/$/g, ''); origin = !safeList.includes(origin) ? '' : origin; res.header("Access-Control-Allow-Origin", origin);//核心操作,后端设置允许跨域的网址 }); -
设置值为 “*”—允许所有源访问
-
如果服务器设置了res.header(“Access-Control-Allow-Origin”,“*”),客户端不允许携带跨域资源凭证
-
但就算服务器端设置 res.header(“Access-Control-Allow-Credentials”, true)
-
客户端也 不允许携带跨域资源凭证
-
代码示例
-
后端服务器
/*-CREATE SERVER-*/ const express = require("express"); let app = express(); app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`)); /*-MIDDLE WARE-*/ // 设置白名单 app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*");//核心操作,后端设置允许跨域的网址 res.header("Access-Control-Allow-Credentials", true);//设置 res.header("Access-Control-Allow-Credentials", true),客户端也 不允许携带跨域资源凭证 req.method === "OPTIONS" ? res.send("OK") : next(); }); /*-API-*/ app.get("/list", (_, res) => { res.send({ code: 0, message: "zhufeng", }); }); /* STATIC WEB */ app.use(express.static("./"));-
前端代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>1</title> </head> <body> <script src="./axios.min.js"></script> <script> axios .get("http://127.0.0.1:1001/list", { // withCredentials: true,//后端res.header("Access-Control-Allow-Origin","*")或 res.header("Access-Control-Allow-Credentials", false)时会报错 withCredentials: false, }) .then((response) => { console.log(0, response); return response.data; }) .then((value) => { console.log(1, value); }) .catch((err) => { console.log(2, err); }); </script> </body> </html>
-
-
-
-
-
-
-
-
proxy 跨域代理 客户端(必须会)
-
使用方式的种类
- webpack(vue)
- nodejs
- nginx
-
原理: 服务器与服务器之间没有域的限制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ASdUqQOn-1680969492598)(./proxy代理服务器跨域原理.png)]-
在客户端与跨域目标服务器之间设置一个由前端控制的代理服务器
-
客户端向代理服务器发送一个请求
-
这里要保证代理服务器网络路径与客户端代码网络路径是同域的
- webpack(vue)的方式,就是改
vue项目根路径/vue.config.js中的module.exports对象中的devServer.proxy属性,一个属性名代表一个代理服务器路径。 - nodejs的方式,就是把前端html文件移动到当前网络服务监听到的静态目录中。
- nginx的方式,就是改nginx的配置项。
- webpack(vue)的方式,就是改
-
也就是说,要让客户端与代理服务器无跨域问题
- 要保证客户端与代理服务器的协议、域名、端口号一致
-
-
代理服务器收到客户端的请求,就把请求路径改一下,转发给跨域目标服务器
- 由于代理服务器是服务器,是没有跨域限制的,所以请求会成功
-
跨域目标服务器收到代理服务器,把结果给到代理服务器
-
代理服务器收到跨域目标服务器的结果,把跨域目标服务器结果转发给客户端
-
-
实际步骤(以nodejs为域)
- 写好nodejs代码,监听一个网络端口,并定义静态目录
- 把前端html文件及相当依赖放到静态目录中,让其与nodejs服务同域
- 运行nodejs代码,监听具体端口
- 以网络方式打开html文件
-
例子
-
前端代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>2proxy.html</title> </head> <body> 当前文件要放在服务器根目录中,即 http://127.0.0.1:1001/2proxy.html 这个路径对应的文件夹里 <script src="./axios.min.js"></script> <script> axios .get("http://127.0.0.1:1001/aaa", { // withCredentials: true, withCredentials: false, }) .then((response) => { console.log(0, response); return response.data; }) .then((value) => { console.log(1, value); }) .catch((err) => { console.log(2, err); }); </script> </body> </html> -
后端代码
/*-CREATE SERVER-*/ const express = require('express'); let request = require('request'); let app = express(); app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`)); // 服务器接收到客户端发送过来的请求 app.get('/aaa', (req, res) => { // 向简书发送相同请求,从简书服务器获取想要的数据「不存在域的限制的」 let jianURL = `https://www.jianshu.com/asimov/subscriptions/recommended_collections`; req.pipe(request(jianURL)).pipe(res); }); /* STATIC WEB */ app.use(express.static('./'));
-
-
-
jsonp 客户端(老)
-
jsonp跨域 --> get
- jsonp跨域只能对get请求生效
- 而且一般jsonp要配合动态生成script标签来使用。
-
原理: link(href)、img(src)、script(src)都没有域的限制,获取数据的方式都是get,使用script标签进行跨域
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcyeHIbA-1680969492600)(./script标签jsonp跨域原理.png)]- 客户端代码里,定义一个全局函数,函数名随意,如jsonpCallback。
-
实际步骤(以nodejs为域)
-
jsonp得后端配合,感觉很麻烦
-
-
postMessage+iframe(太老)
-
postMessage、window.name、document.domin、location.hash
- 这些方案结合 iframe 也可以实现跨域处理
-
-
工作中跨域的场景
前端工作中一定会跨域,开发环境一定会跨域,但服务器环境看公司
-
大公司有钱,多个服务器
-
文件专门放不同服务器,客户端根据需求访问不同的服务器,就需要跨域
- 图片专门设置一个服务器(A)
- 压缩包专门设置一个服务器(B)
- 其它数据文件服务器©
-
生产环境和开发环境都会跨域
-
-
小公司没钱,就一个服务器
-
开发过程中,不会将前端代码放到服务器上
- 开发环境中一定会跨域,开发结束才用git放到服务器上
-
最终开发完成后,有可能放到同一个服务器上
- 有可能生产环境不跨域
-
cors跨域
-
前端代码 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>1</title> </head> <body> <script src="./axios.min.js"></script> <script> axios .get("http://127.0.0.1:1001/list", { // withCredentials: true,//后端res.header("Access-Control-Allow-Origin","*")或 res.header("Access-Control-Allow-Credentials", false)时会报错 withCredentials: false, }) .then((response) => { console.log(0, response); return response.data; }) .then((value) => { console.log(1, value); }) .catch((err) => { console.log(2, err); }); </script> </body> </html> -
后端代码-后端设置
-
设置白名单–修改白名单,一定要重新启动服务
/*-CREATE SERVER-*/ const express = require("express"), app = express(); app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`)); /*-MIDDLE WARE-*/ // 设置白名单 let safeList = ["http://127.0.0.1:8848", "http://127.0.0.1:5500"]; app.use((req, res, next) => { let origin = req.headers.origin || req.headers.referer || ""; origin = origin.replace(/\/$/g, ""); origin = !safeList.includes(origin) ? "" : origin; res.header("Access-Control-Allow-Origin", origin); res.header("Access-Control-Allow-Credentials", true); req.method === "OPTIONS" ? res.send("OK") : next(); }); /*-API-*/ app.get("/list", (_, res) => { res.send({ code: 0, message: "zhufeng", }); }); /* STATIC WEB */ app.use(express.static("./")); -
设置值为 “*”—允许所有源访问
/*-CREATE SERVER-*/ const express = require("express"), app = express(); app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`)); /*-MIDDLE WARE-*/ app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", '*'); req.method === "OPTIONS" ? res.send("OK") : next(); }); /*-API-*/ app.get("/list", (_, res) => { res.send({ code: 0, message: "zhufeng", }); }); /* STATIC WEB */ app.use(express.static("./"));
-
proxy跨域代理服务器
-
前端代码 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> 2. proxy 跨域代理 客户端(必须会) webpack(vue)/nodejs/nginx 客户端:http://127.0.0.1:5500/2023_01/JS%E9%83%A8%E5%88%86/day0408/2.html 将客户端的页面(2.html),放到服务器(CrossDomain)上, 用服务器的方式打开页面(2.html),不能使用 open with live server http://127.0.0.1:1001/2.html 代理服务器:http://127.0.0.1:1001/aaa 服务器:https://www.jianshu.com/asimov/subscriptions/recommended_collections <script src="axios.min.js"></script> <script> axios.get("http://127.0.0.1:1001/aaa") .then(res=>{ return res.data }).then(value=>{ console.log(value); }).catch(err=>{ console.log(err); }) </script> </body> </html> -
后端代码-后端设置
/*-CREATE SERVER-*/ const express = require('express'), request = require('request'), app = express(); app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`)); // 服务器接收到客户端发送过来的请求 app.get('/aaa', (req, res) => { // 向简书发送相同请求,从简书服务器获取想要的数据「不存在域的限制的」 let jianURL = `https://www.jianshu.com/asimov/subscriptions/recommended_collections`; req.pipe(request(jianURL)).pipe(res); }); /* STATIC WEB */ app.use(express.static('./'));
jsonp跨域
jsonp的展示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<script>
function fun(data) {
console.log('页面每新增一个调用jsonp对应的get类型url并且配置了回调函数为fun时的script标签,就会调用一下',data);
}
</script>
<script src="https://www.baidu.com/sugrec?prod=pc&wd=1&cb=fun"></script>
</body>
</html>
jsonp的nodejs演示
-
前端代码index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> 3. jsonp跨域---》get link(href),img(src),script(src) 都没有域的限制,获取数据的方式都是get 使用script来实现跨域 <script> function fun(data){ console.log(data); } </script> <script src="http://127.0.0.1:1001/user/list?callback=fun"></script> </body> </html> -
后端代码
/*-CREATE SERVER-*/ const express = require('express'), app = express(); app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`)); app.get('/user/list', (req, res) => { // 获取传递进来的callback值,例如:'func' let { callback } = req.query; // 准备数据 let result = { code: 0, data: ['张三', '李四'] }; // 返回给客户端指定的格式,例如:’函数名(数据)‘ res.send(`${callback}(${JSON.stringify(result)})`); }); /* STATIC WEB */ app.use(express.static('./'));
动态jsonp的使用-百度联想词组-配合动态script标签
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" id="inp" >
<ul id="ulbox">
</ul>
<script>
let key="";
inp.onkeydown=function(){
key=inp.value;
let script=document.createElement("script");
script.id="aaa";
script.src=`https://www.baidu.com/sugrec?prod=pc&wd=${key}&cb=func`;
document.body.appendChild(script);
}
function func(data){
console.log(data);
let arr=data.g||[];
let str="";
arr.forEach(item=>{
let {q}=item;
str+=`<li>${q}</li>`
})
ulbox.innerHTML=str;
let scriptAAA=document.getElementById("aaa");
document.body.removeChild(scriptAAA);
}
</script>
</body>
</html>
jsonp函数封装
-
前端代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script src="jsonp.js"></script> <script> // jsonp(url,config)---》Promise实例 jsonp("http://127.0.0.1:1001/user/list", { jsonpName: "callback", }) .then((value) => { console.log(value); }) .catch((err) => { console.log(err); }); jsonp("https://www.baidu.com/sugrec", { jsonpName: "cb", params: { prod: "pc", wd: 1, }, }) .then((value) => { console.log(value); }) .catch((err) => { console.log(err); }); </script> </body> </html>/* 封装一个jsonp方法「基于promise管理」,执行这个方法可以发送jsonp请求 jsonp([url],[options]) options配置项 + params:null/对象 问号参数信息 + jsonpName:'callback' 基于哪个字段把全局函数名传递给服务器 + ... */ (function () { //是不纯对象 const isPlainObject = function isPlainObject(obj) { let proto, Ctor; if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") return false; proto = Object.getPrototypeOf(obj); if (!proto) return true; Ctor = proto.hasOwnProperty('constructor') && proto.constructor; return typeof Ctor === "function" && Ctor === Object; }; //将纯对象--》urlencoded格式 const stringify = function stringify(obj) { let keys = Reflect.ownKeys(obj), str = ``; keys.forEach(key => { str += `&${key}=${obj[key]}`; }); return str.substring(1); }; const jsonp = function jsonp(url, options) { if(typeof url!=="string"){ throw new Error("url必须是字符串,必须填写"); } //判断 options 是不是纯对象,不是就转化为纯对象 if(!isPlainObject(options)){ options={} } //设置默认值 params:null/jsonpName:'callback' options=Object.assign({ params:null, jsonpName:'callback' },options) return new Promise((resolve,reject)=>{ let {params,jsonpName}=options; //函数一定要是全局的,并且是唯一的 // let funName="fun_"+new Date().getTime(); let funName = "fun_" + new Date().getTime()+Math.random().toString().slice(3,9); window[funName]=function(data){ resolve(data); clear(); } //有 jsonpName if(jsonpName){ url+=`${url.includes("?")?"&":"?"}${jsonpName}=${funName}` } //检查params 有并且是纯对象---》urlencoded if(params&&isPlainObject(params)){ url+=`${url.includes("?")?"&":"?"}${stringify(params)}` } //清除函数 function clear(){ //script 标签删除 let myScript=document.getElementById(funName); document.body.removeChild(myScript); //全局函数删除 delete window[funName] } let script=document.createElement("script"); script.id=funName; script.src=url; document.body.appendChild(script); //如果请求失败 script.onerror=function(err){ reject(err); clear(); } }) }; /* 暴露API */ if (typeof module === 'object' && typeof module.exports === 'object') module.exports = jsonp; if (typeof window !== 'undefined') window.jsonp = jsonp; })(); -
后端代码
/*-CREATE SERVER-*/ const express = require('express'), app = express(); app.listen(1001, () => console.log(`服务启动成功,正在监听1001端口!`)); app.get('/user/list', (req, res) => { // 获取传递进来的callback值,例如:'func' let { callback } = req.query; // 准备数据 let result = { code: 0, data: ['张三', '李四'] }; // 返回给客户端指定的格式,例如:’函数名(数据)‘ res.send(`${callback}(${JSON.stringify(result)})`); }); /* STATIC WEB */ app.use(express.static('./'));
Object.assign()方法
-
Object.assign(前面对象,后面对象)用于合并多个对象的属性值。
-
返回一个综合了多个对象所有键值对的新对象。
-
后面对象的属性会把前面对象的同名属性的属性值给覆盖掉。
- 如果前面对象有,但后面对象没有的属性,用的是前面对象的属性值。
- 如果前面对象有,后面对象也有的属性,用的是后面对象的属性值。
- 如果前面对象没有,后面对象有的属性,用的后面对象的属性值。
- 前面对象和后面对象都没有的属性,新的对象就没有。
-
类似于{…前面对象,…后面对象}。
-
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let obj={
name:"lili",
age:10
}
let obja={
name:"Tom",
n:100
}
//合并对象
//后面会把前面有的给覆盖,
//后面多出的,保留
//前面没有的,保留
let objb=Object.assign(obj,obja);
console.log(objb);//{name: 'Tom', age: 10, n: 100}
</script>
</body>
</html>