文章目录

  • 跨域
    • 什么是跨域
    • 前端解决: JSONP
      • 实现原理
      • 步骤
        • 前端:创建标签,拼接传递参数
        • 后端:接收值,返回值
      • 封装 Ajax 代码
      • 在封装的 Ajax 中添加 JSONP
        • 需求
        • 思路
      • 练习:JSONP 请求 百度模糊搜索 接口
      • 瀑布流加载数据的的实现思路
    • 后端解决:CORS 跨域资源共享
      • 设置响应头
      • 预检请求
        • 介绍
        • 预检请求发生的条件
        • 处理预检请求
        • 预检请求的有效期
        • Chrome 没有 Options 请求的问题(已解决,因为自己傻掉了,忘记关掉过滤)
    • 后端解决:后端代理
      • 思路
    • 奇怪的错误

跨域

什么是跨域

跨域是浏览器为了安全而报的错误,如果不同源去请求资源,那么就会报跨域的错误。

同源概念:协议,域名,端口号一致

报错类似如下

Access to XMLHttpRequest at 'http://localhost:4000/getAjax' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

跨域是后台接口已经通了,但是浏览器拦截掉了,浏览器处于安全角度考虑,拦截掉了数据。

前端解决: JSONP

只支持 get 方式,不支持 post 方式

实现原理

html 中有些标签是没有同源限制的,比方说 scriptiframeimg 等,其中用到的就是 script

<script src="..."></script>
<img>
<link>
<iframe>

那么,我通过创建 script 标签,去访问跨域的资源,然后拿到资源就可以了~

步骤

前端:创建标签,拼接传递参数

前端:创建一个 script 标签,写上链接,可以看到参数跟 get 请求差不多,都是 queryString

let button = document.getElementById("btn");
button.addEventListener("click", () => {let myScript = document.createElement("script");    // 创建游离的 script 标签myScript.src = "http://localhost:4000/getAjax?name=1"; // 添加 src 属性document.querySelector("head").appendChild(myScript); // 插入游离的标签到 head 标签中
});

后端:接收值,返回值

我们知道,前端引入了标签 script ,就会向后端发送个请求,去寻找资源。

后端通过 ctx.query 来获取前端传来的值

router.get('/getAjax', ctx => {console.log('请求到了');console.log(ctx.query);ctx.body = 'var a = 1'
})

然而这里会有个拐弯儿:后端返回的是 前端要执行的代码!!!

这里还会有一个问题,前端如何接收后端传来的东西呢,我如果直接打印 a,会 undefined

let button = document.getElementById("btn");
button.addEventListener("click", () => {let myScript = document.createElement("script");myScript.src = "http://localhost:4000/getAjax?name=1";document.querySelector("head").appendChild(myScript);console.log(a);       // undefined
});

这是因为异步了,可以等待标签加载完后再接收,如下所示:

let button = document.getElementById("btn");
button.addEventListener("click", () => {let myScript = document.createElement("script");myScript.src = "http://localhost:4000/getAjax?name=1";document.querySelector("head").appendChild(myScript);myScript.onload = function() {// 当标签加载完成后打印变量console.log(a); // 1}
});

但是这样处理不是很好

有一个方法:我可以跟后端商量好一个函数名称,后台返回函数执行命令,这样后台处理完之后发送一个执行函数的命令,我前端执行函数即可。

这里会有一点绕,慢点看

前端代码:

let button = document.getElementById("btn");
button.addEventListener("click", () => {let myScript = document.createElement("script");myScript.src = "http://localhost:4000/getAjax?name=1&cb=cbfunc";    // 这里我会传递一个函数名,等待后端来发送执行命令document.querySelector("head").appendChild(myScript);
});
const cbfunc = (res) => {console.log(res);     // 这个函数名已经定义好了,就等着后端来调用了,在这里能够获得到后端传来的值
};

后端:

router.get('/getAjax', ctx => {console.log('请求到了');console.log(ctx.query);let cb = ctx.query.cb;      // 后端获取到前端传来的函数名let data = {   // 后端要返给前端的参数a: 1,b: 2,c: 3}ctx.body = `${cb}(${JSON.stringify(data)})`       // 注意序列化// 上面已经说了,这里返回前端能够执行的代码,所以这里就是一个前端函数执行的语句,类似这样:funcName()// 也就是后端命令前端去执行它了
})

这样就较好的解决异步问题了,也是 jsonp 的雏形

封装 Ajax 代码

这个是上一章的内容了,这里就是封装了一下,

function ajax(options) {let opts = Object.assign({method: "get",url: "",headers: {"content-type": "application/x-www-form-urlencoded",},jsonp: "cb",data: "",success: function () {},},options);let xhr = new XMLHttpRequest();if (options.method == "get") {let data = o2u(opts.data);options.url = options.url + "?" + data;}xhr.open(options.method, options.url, true);for (let key in opts.headers) {xhr.setRequestHeader(key, opts.headers[key]);}let sendData;switch (opts.headers["content-type"]) {case "application/x-www-form-urlencoded":sendData = o2u(opts.data);break;case "application/json":sendData = JSON.stringify(opts.data);break;}xhr.onload = function () {let resData;if (xhr.getResponseHeader("content-type").includes("xml")) {resData = xhr.responseXML;} else {resData = JSON.parse(xhr.responseText);}options.success(resData);};if (options.method == "get") {xhr.send();} else {xhr.send(sendData);}
}function o2u(obj) {let keys = Object.keys(obj);let values = Object.values(obj);return keys.map((v, k) => {return `${v}=${values[k]}`;}).join("&");
}

在封装的 Ajax 中添加 JSONP

我们在使用自己封装的ajax 的时候,调用的形式大概是这个样子的

ajax({url: "http://localhost:4000/getAjax",data: {name: "你好",age: 10,},dataType: "jsonp",        // 这里说明是跨域访问success(res) {          // 这是成功回调函数,稍后要处理它,是个小难点console.log(res);},
});

需求

我们希望在原有的 ajax 上添加跨域的功能

思路

  • 首先判断一下是不是跨域请求
  • 如果是的话,就去创建个 script 标签,
  • 设置它的 src 属性:传递的参数,回调函数的名称等
  • 把这个 script 标签 appendChildhead 中去!

做着做着你就会遇到这个问题:如何处理这个成功回调参数?

因为你观察咱们的调用方式,是通过 success() 处理返回值的,它你需要重新设置个名字的,不然直接拼接的话就成这个样子了

http://localhost:4000/getAjax?name=你好&age=10&cb=success(){}

这样肯定不行的啊。

解决的思路就是随机一个函数名,然后在 window 对象上定义一下函数,然后把 success() 语句 赋值给这个函数

function jsonpFunc(url, data, cbName, cbFunc) {let randomFunc ="myRandomFunciotn" + Math.random().toString().substr(2);      // 这样做防止函数重名window[randomFunc] = cbFunc; // window 注册此函数let path = `${url}?${o2u(data)}&${cbName}=${randomFunc}`;// console.log(path);let myScript = document.createElement("script");myScript.src = path;document.querySelector("head").appendChild(myScript);
}

解释的代码如下:

我们来进行证明:

  • 首先我来创建个跨域的 ajax 请求,成功回调函数体里写点东西哈
document.querySelector('button').addEventListener('click', function () {ajax({url: 'http://localhost:4000/getAjax',data: {name: 'hello',age: 10,},dataType: 'jsonp',success(res) {console.log('函数体:我是 success 成功回调函数的内容')},})
})
  • 接着点击按钮,发送请求

  • 如何去验证呢? 去 window 对象下找函数去!!!我们打印 window 对象

  • ok!验证成功

完整代码如下:这里也有 gitee 代码仓库 https://gitee.com/lovely_ruby/DailyPractice/tree/main/front/07/JSONP,可以克隆到本地

<script>document.querySelector("button").addEventListener("click", function () {ajax({url: "http://localhost:4000/getAjax",data: {name: "你好",age: 10,},dataType: "jsonp",success(res) {console.log(res);},});});function ajax(options) {let opts = Object.assign({method: "get",url: "",headers: {"content-type": "application/x-www-form-urlencoded",},jsonp: "cb",data: "",success: function () {},},options);if (opts.dataType === "jsonp") {// 做跨域的处理jsonpFunc(opts.url, opts.data, opts.jsonp, opts.success);return; // 这里记得return截断}function jsonpFunc(url, data, cbName, cbFunc) {let randomFunc ="myRandomFunciotn" + Math.random().toString().substr(2); // substr 截取window[randomFunc] = cbFunc; // window 注册此函数let path = `${url}?${o2u(data)}&${cbName}=${randomFunc}`;// console.log(path);let myScript = document.createElement("script");myScript.src = path;document.querySelector("head").appendChild(myScript);}let xhr = new XMLHttpRequest();if (options.method == "get") {let data = o2u(opts.data);options.url = options.url + "?" + data;}xhr.open(options.method, options.url, true);for (let key in opts.headers) {xhr.setRequestHeader(key, opts.headers[key]);}let sendData;switch (opts.headers["content-type"]) {case "application/x-www-form-urlencoded":sendData = o2u(opts.data);break;case "application/json":sendData = JSON.stringify(opts.data);break;}xhr.onload = function () {let resData;if (xhr.getResponseHeader("content-type").includes("xml")) {resData = xhr.responseXML;} else {resData = JSON.parse(xhr.responseText);}options.success(resData);};if (options.method == "get") {xhr.send();} else {xhr.send(sendData);}}// 把对象传换成 queryString,就是 get 请求后面的 ? &function o2u(obj) {let keys = Object.keys(obj);let values = Object.values(obj);return keys.map((v, k) => {return `${v}=${values[k]}`;}).join("&");}
</script>

练习:JSONP 请求 百度模糊搜索 接口

写在另一篇文章里了,点击这里跳转

瀑布流加载数据的的实现思路

检测滚动条是否到达底部,如果到达底部了就去加载文件

document.onscroll = function () {let windowHeight = document.documentElement.clientHeight; // 用户窗口的大小let contentHeight = document.documentElement.offsetHeight; // 页面总共的大小let scrollHeight = contentHeight - windowHeight; // 计算出滚动条的高度let scrollTop = document.documentElement.scrollTop; // 获取当前滚动条的高度console.log('windowHeight:>>', windowHeight);console.log('contentHeight:>>', contentHeight);console.log('scrollTop:>>', scrollTop);if (scrollTop > scrollHeight - 10) {// 加载下一页数据的逻辑}
};

后端解决:CORS 跨域资源共享

既然浏览器是为了安全考虑的,那么后端就可以在返回头中告诉浏览器安全即可。

解决的一个思路是:后端在返还头中给个标识,告诉浏览器不要去拦截

设置响应头

ctx.set("Access-Control-Allow-Origin","*");
// 允许所有源,任何人请求这个接口都可以,会在返回头中写这么一段,告诉浏览器安全了,但是不建议全都通过,不建议这样写ctx.set("Access-Control-Allow-Origin","http://localhost:3000");  // 只是允许本地 3000 访问,建议这样写,指定域名

写法类似如下图所示:

插叙:但是你这样设置,前端浏览器可能也报错,就像我下面列举的奇怪的错误中图片描述的,CORS 并不好使,这可能是因为预检请求的原因,解决的办法就是下面描述的,加上 options 的请求

如果设置了规则为通配符*的话,你会发现 cookie 没了
一是因为用了通配符,用了通配符的话就不让携带 cookie
二是要设置允许携带凭证,withCredentials ,(cookie 其实也算是一种凭证)。

设置请求头的样子如下图所示:


预检请求

介绍

复杂请求的时候,请求真正接口前先去探探路,看看服务器让不让访问,类似敢死队?

浏览器的同源策略,就是出于安全考虑,浏览器会限制从脚本发起的跨域 HTTP 请求(比如异步请求GET, POST, PUT, DELETE, OPTIONS等等),所以浏览器会向所请求的服务器发起两次请求

  • 第一次是浏览器使用OPTIONS方法发起一个预检请求
  • 第二次才是真正的异步请求

第一次的预检请求获知服务器是否允许该跨域请求:如果允许,才发起第二次真实的请求;如果不允许,则拦截第二次请求。
Access-Control-Max-Age用来指定本次预检请求的有效期,单位为秒,,在此期间不用发出另一条预检请求

预检请求发生的条件

如果你是解决跨域,并且是通过 CORS 解决的,就会有这种预检请求

预检请求发送条件:简单的请求没有预检请求,比方说

  • 请求方式是 getpostheadcontent-type 只有如下几条时

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded

会被认为是简单请求

除了简单请求,其他的情况都算是复杂请求,需要发送预检请求

处理预检请求

随便给他们返还点东西即可,但是也要设置跨域,还有请求头

router.options('/*', (ctx) => {console.log('走了options')ctx.set('Access-Control-Allow-Origin', 'http://localhost:3000') // 预检请求也需要允许跨域ctx.set('Access-Control-Allow-Headers','Content-Type, Content-Length, Authorization, Accept, X-Requested-With , mytest')ctx.body = {}
})

预检请求的有效期

我们会发现每次请求都需要走两次接口,性能不太好,所以成功一次的话可以把这个缓存记录下来,下次就只请求一次了

// 在预检请求中设置返回头的信息
ctx.set('Access-Control-Max-Age', 10)   // 单位是秒

测试如下:

Chrome 没有 Options 请求的问题(已解决,因为自己傻掉了,忘记关掉过滤)

以下是 Chrome 老版本的截图

最新的 Chrome 浏览器没有这个报错(猜测),而是如下这样子(目前:2021年7月)

所以换家浏览器, 用火狐来测试。


不对,是我傻掉了,我把这个过滤掉了,放开过滤就好了!!!


后端解决:后端代理

思路

既然跨域是浏览器的安全策略,那么就让后端去访问跨域的链接,后端和后端之间的访问就没有跨域这一说了,绕过了浏览器!

后端服务器去请求跨域的资源后,再返还给前端

主要的是知道数据流是怎么走的,知道思路!!!


奇怪的错误

问题:后台设置 CORS 不好使 ?? 浏览器还报奇怪的错误

中文路径的问题,把路径里的中文改成英文的就好了,淦!!!


我试了试,又报哪个错误了,然后把这句话加上之后,就好了,但是你如果注释掉,他虽然是好使的,但是等一会儿就不好使了,报跨域的错误了

router.options("/*",ctx=>{ctx.set("Access-Control-Allow-Origin","http://localhost:3000");ctx.set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Authorization, Accept, X-Requested-With , mytest");ctx.set("Access-Control-Max-Age",600);ctx.body = "";
})

时效性是因为我设置了 Access-Control-Max-Age",600 这个东西

【前端44_前后端交互_跨域】前端解决:JSONP、后端解决:CORS 、后端代理相关推荐

  1. 解决vue项目中前后端交互的跨域问题、nginx代理配置

    最近新接了一个项目,需要自己写一个小demo和后端联调,第一次用自己写的demo和后端联调,出现了问题. 问题:Access to XMLHttpRequest at 'http://192.168. ...

  2. go设置后端启动_Vue 之前后端分离的跨域

    阅读本文约需要9分钟 大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈).上次老师跟大家分享了JS 之继承的知识,今天跟大家分享下Vue 之前后端分离 ...

  3. Spring Boot2.x-13前后端分离的跨域问题解决方法之Nginx

    文章目录 概述 浏览器同源策略 后台搭建 pom.xml interceptor 配置 Controller 启动测试 浏览器和session 后端工程发布到服务器上 问题复现 通过Nginx反向代理 ...

  4. 06-若依前后端分离项目跨域问题解决方式

    什么是跨域 跨域就是前后端分离项目前端无法把session等信息传递给后端服务器.跨域源自浏览器同源策略.同源策略是一种约定,同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互. ...

  5. Vue+Flask前后端分离 Vue3跨域配置

    Vue+Flask前后端分离 Vue3跨域配置 前端端口号为8080 后端端口号为5000 问题描述 问题解决 接口路径映射 前端端口号为8080 后端端口号为5000 后端端口API 代码片. @a ...

  6. 解决java前后端分离端口跨域问题

    解决java前后端分离端口跨域问题 参考文章: (1)解决java前后端分离端口跨域问题 (2)https://www.cnblogs.com/mollie-x/p/10449686.html 备忘一 ...

  7. 前端必须懂的计算机网络知识—(跨域、代理、本地存储)

    前端必须懂的计算机网络知识系列文章: DNS服务器和跨域问题 计算机网络的分层模型 IP地址和MAC地址 前端必须懂的计算机网络知识-(跨域.代理.本地存储) 前端必须懂的计算机网络知识-(TCP) ...

  8. 什么是跨域,后端工程师如何处理跨域

    什么是跨域 前言 作为一名后端开发工程师,在给前端同事写接口的时候,经常碰到他们讲,你的接口跨域了,那么什么是跨域,这里来研究下. 什么是跨域 先来看下跨域的定义 跨域的广义定义:跨域是指一个域下的文 ...

  9. 22-CMS前端页面查询开发-Api调用-跨域解决

    跨域问题解决 测试 上边的代理 ,结果 报错如下 : No 'Access-Control-Allow-Origin' header is present on the requested resou ...

最新文章

  1. shell getopts命令
  2. sql server 2005 T-SQL @@TOTAL_READ (Transact-SQL)
  3. 业内首款云原生技术中台产品云原生 Stack 来了!
  4. Python格式化字符串知多少
  5. 笔记-项目整体管理-项目工作说明书
  6. dwz ajax session超时跳转登录页(struts2自定义拦截器)
  7. Python项目打包发布到pypi
  8. java文件操作和_JAVA文件操作类和文件夹的操作
  9. 文件夹没有安全选项-文件上传下载-路径访问被拒绝
  10. 速度更新!GoCD又曝仨洞,极易遭利用且结合利用可成供应链攻击的新跳板
  11. 《Android游戏开发详解》一1.8 控制流程第2部分——while和for循环
  12. Java应用开发的一条重要经验:先建立基础设施
  13. Ubuntu下安装使用Monaco字体
  14. 数据库索引类型介绍及其优缺点、区别、适用场景
  15. python词频统计西游记_基于Python第三方插件实现西游记章节标注汉语拼音
  16. 百度搜索引擎结果网址参数 搜索框提示词搜索方式(rsv_sug2)
  17. 网页崩溃原因软件测试,支招:原来这些才是APP崩溃的主要原因!资深技术大牛测试经验总结...
  18. 明日方舟公式计算机,【科普向】明日方舟里的伤害计算公式
  19. linux下cp -b,Linux下cp的命令解释
  20. 限流的方式,为什么要限流,怎么实现限流

热门文章

  1. 大厨小鲜——自己动手实现一个极简Web框架
  2. PS3文件的打开新建及储存,PS4移动工具图层感念,PS5矩形选框工具组选区感念,
  3. 2019 KDD accpeted paper2019年kdd接收的论文
  4. js合并两个json对象
  5. 小黄车无法在线退还余额 累计负债达64.96亿 这一次ofo真的要凉了吗?
  6. 动态获取API函数地址---对抗win7 aslr安全机制
  7. Android的软键盘和主题冲突的解决方法
  8. 【iPhone8iPhone X】高科技技术必须学会的人脸识别术
  9. 彩印1000张a4纸多少钱?
  10. 网络爬虫快速入门(二)