20230408----重返学习-JavaScript前端跨域方案
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>
-
-
- 代码示例
- 客户端也 不允许携带跨域资源凭证
- 但就算服务器端设置 res.header(“Access-Control-Allow-Credentials”, true)
- 如果服务器设置了res.header(“Access-Control-Allow-Origin”,“*”),客户端不允许携带跨域资源凭证
-
-
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得后端配合,感觉很麻烦
- jsonp跨域 --> get
-
postMessage+iframe(太老)
- postMessage、window.name、document.domin、location.hash
- 这些方案结合 iframe 也可以实现跨域处理
- postMessage、window.name、document.domin、location.hash
-
工作中跨域的场景
前端工作中一定会跨域,开发环境一定会跨域,但服务器环境看公司
- 大公司有钱,多个服务器
- 文件专门放不同服务器,客户端根据需求访问不同的服务器,就需要跨域
- 图片专门设置一个服务器(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 serverhttp://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跨域---》getlink(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();}//有 jsonpNameif(jsonpName){url+=`${url.includes("?")?"&":"?"}${jsonpName}=${funName}`}//检查params 有并且是纯对象---》urlencodedif(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>