浏览器默认存在安全访问限制:如果从当前源从另外一个源发送数据请求默认是不允许的
1.如何区分是同源还是跨域?
web页面:http://127.0.0.1:5500
数据接口:www.jianshu.com:443
- 俩个地址对于,如果:协议、域名、端口号三者有一个不一致,都是跨域请求
- 跨域错误:
Access to XMLHttpRequest at ‘www.jianshu.com/asimov/subs…‘ from origin ‘http://127.0.0.1:5500‘ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
2.跨域在项目中的场景和意义?
1)开发的时候是跨域的,但是项目部署上线后是同源的[现在很少了]
- 只需要解决开发时候的跨域问题即可
- 解决办法:修改本地的host文件即可[原理:构件本地DNS解析缓存]
//开发时
web页面:http://127.0.0.1:5500
数据地址:http://www.xiaozong.com/api/...
//部署的时候
web页面:http:///www.xiaozong.com/index.html
数据地址:http://www.xiaozong.com/api/..
//修改本地host文件
www.xiaozong.com:80 127.0.0.1:5500
后期在自己电脑的浏览器中访问www.xiaozong.com,其实最后访问的是127.0.0.1,但是浏览器认为我们的web地址是http://www.xiaozong.com,所以本质是“欺骗”浏览器...
复制代码
2)开发和部署的时候都是跨域的
- 为实现服务器资源的合理利用,我们一个项目都是分服务器部署的【web服务器、数据服务器、图片服务器…】
- 需要请求第三方平台数据
- …..
3.跨域的解决方案
1)JSONP跨域请求方案[局限性:这能支持GET请求]
- link/script/img…都不存在域的限制,直接可以跨域请求资源{请求方式是:get}
- Object.assign(obj1,obj2):合并两个对象,让obj2中的信息替换obj1中的信息,返回的是obj1的地址「obj1对象被修改了」
- 利用script不存在域的限制,的特点,我们再发送数据请求,不再使用axios和fetch了,直接基于script的src发送请求即可【这样避开了域的限制】,但是他有很强的局限性:因为script都是GET的请求,所以我们能够发送的数据请求,也只能是GET。
- 为啥是全局函数:
- 服务器返回的格式,浏览器会帮助我们执行,此时函数如果不是全局的,根本不能拿到函数,就会报错
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;
};
// jsonp方式
const jsonp = function jsonp(config) {
// 默认配置项 & 合并配置项
let initial = {
url: '',
params: null,
jsonpCallback: 'callback',
success: () => {}
};
if (!isPlainObject(config)) config = {};
let {
url,
params,
jsonpCallback,
success
} = Object.assign({}, initial, config);
// 处理PARAMS
if (params !== null) {
// 如果传递的是对象,我们把其变为URLENCODED格式字符串
if (isPlainObject(params)) params = Qs.stringify(params);
// 把参数拼接到URL的末尾
url += `${url.includes('?')?'&':'?'}${params}`;
}
// 把一个全局函数的名字,基于jsonpCallback指定的字段,拼接到URL末尾,传递给服务器
let fnname = `jsonp${+new Date()}`;
window[fnname] = function (value) {
// value从服务获取的结果,把回调函数执行
if (typeof success === 'function') success(value);
// 清除一些没用的值
delete window[fnname];
document.body.removeChild(script);
};
url += `${url.includes('?')?'&':'?'}${jsonpCallback}=${fnname}`;
// 动态创建SCRIPT,发送数据请求
let script = document.createElement('script');
script.src = url;
document.body.appendChild(script);
};
//修改为promise版本的jsonp
const jsonpPromise = function jsonpPromise(config) {
if (!isPlainObject(config)) config = {};
let fnname = `jsonp${+new Date()}`,
script;
return new Promise(resolve => {
let {
url,
params,
jsonpCallback,
} = Object.assign({
url: '',
params: null,
jsonpCallback: 'callback'
}, config);
if (params !== null) {
if (isPlainObject(params)) params = Qs.stringify(params);
url += `${url.includes('?')?'&':'?'}${params}`;
}
window[fnname] = function (value) {
delete window[fnname];
document.body.removeChild(script);
resolve(value);
};
url += `${url.includes('?')?'&':'?'}${jsonpCallback}=${fnname}`;
script = document.createElement('script');
script.src = url;
document.body.appendChild(script);
});
};
// 使用jsonp
jsonp({
url: 'http://127.0.0.1:1001/user/list',
success(value) {
console.log(value);
}
});
//使用jsonpPromise
jsonpPromise({
url: 'https://www.baidu.com/sugrec',
params: {
prod: 'pc',
wd: '珠峰'
},
jsonpCallback: 'cb'
}).then(value => {
console.log(value);
});
复制代码
2)CROS跨域资源共享
- 原理:不允许跨域是因为,当前WEB页面的“源地址origin”向服务器发送请求是不被允许的,所以如果想解决这个问题,只需要服务器端设置为允许即可‘Access-Control-Allow-Origin’
客户端代码
//fetch写法
fetch('http://127.0.0.1:1001/list', {
// 前端设置跨域中:是否允许携带资源凭证「因为服务器设置是可以允许的,前端需要对应上」
// + same-origin:同源才允许
// + include:跨域也允许
credentials: 'include'
}).then(response => {
return response.json();
}).then(value => {
console.log(value);
});
//axios写法
axios.get('http://127.0.0.1:1001/list', {
withCredentials: true
}).then(response => {
console.log(response);
});
复制代码
服务器代码
/*-CREATE SERVER-*/
const express = require('express'),
app = express();
app.listen(1001, () => {
console.log(`THE WEB SERVICE IS CREATED SUCCESSFULLY AND IS LISTENING TO THE PORT:1001`);
});
/*-MIDDLE WARE-*/
// 设置白名单
let safeList = ["http://127.0.0.1:5500", "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-Credentials", true);
// res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length,Authorization, Accept,X-Requested-With");
// res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,HEAD");
// CORS跨域资源共享的时候在发送真实的请求之前,浏览器会先发送一个试探性的请求 OPTIONS「目的:测试客户端和服务器之间是否可以正常的通信」,如果可以正常通信,接下来在发送真实的请求信息!!
req.method === 'OPTIONS' ? res.send('OK') : next();
/* // 设置允许源 Access-Control-Allow-Origin
// + * 允许所有源,这样不安全,所以是不允许携带跨域资源凭证「例如:cookie」
// + 允许单一源 这样是可以携带资源凭证的
// 设置是否允许携带跨域资源凭证 Access-Control-Allow-Credentials
res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:5500');
res.header("Access-Control-Allow-Credentials", true);
next(); */
});
/*-API-*/
app.get('/list', (req, res) => {
res.send({
code: 0,
message: 'zhufeng'
});
});
/* STATIC WEB */
app.use(express.static('./'));
复制代码
3)PROXY跨域资源代理
- 客户端访问的地址由代理服务器提供
- 客户端请求的数据接口,全部由他来进行处理
此时变为:同源请求->WEB页面和请求数据接口都是Proxy代理服务器处理的
- 代理服务器收到客户端请求后,帮助客户端向简书发送请求 “服务器与服务器之间不存在跨域”【除非简书做了处理,拒绝我们代理服务器的请求】
- 代理服务器把获取的数据,再返回给客户端
- 基于nginx就可以创建一个代理服务器【web服务器】->真实项目部署
- 基于webpage-dev-server也可以创建一个代理服务器 -> vue/react开发
- 基于node.js也可以自己写一个代理服务器
4)postMessage + iframe
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END