跨域以及解决方案

浏览器默认存在安全访问限制:如果从当前源从另外一个源发送数据请求默认是不允许的

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对象被修改了」
  1. 利用script不存在域的限制,的特点,我们再发送数据请求,不再使用axios和fetch了,直接基于script的src发送请求即可【这样避开了域的限制】,但是他有很强的局限性:因为script都是GET的请求,所以我们能够发送的数据请求,也只能是GET。
  • 为啥是全局函数:
    • 服务器返回的格式,浏览器会帮助我们执行,此时函数如果不是全局的,根本不能拿到函数,就会报错

JSONP原理.png

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跨域资源代理

  1. 客户端访问的地址由代理服务器提供
  2. 客户端请求的数据接口,全部由他来进行处理

此时变为:同源请求->WEB页面和请求数据接口都是Proxy代理服务器处理的

  1. 代理服务器收到客户端请求后,帮助客户端向简书发送请求 “服务器与服务器之间不存在跨域”【除非简书做了处理,拒绝我们代理服务器的请求】
  2. 代理服务器把获取的数据,再返回给客户端
  • 基于nginx就可以创建一个代理服务器【web服务器】->真实项目部署
  • 基于webpage-dev-server也可以创建一个代理服务器 -> vue/react开发
  • 基于node.js也可以自己写一个代理服务器

微信图片_20210607201740.jpg

4)postMessage + iframe

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享