JSONP 方式实现跨域请求数据

2024-04-12 21:32

本文主要是介绍JSONP 方式实现跨域请求数据,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

在了解 浏览器的同源政策(SOP) 后知道了浏览器的同源政策对保护用户信息安全的重要性。但是有些时候我们确实需要两个网站间的数据共享。例如腾讯天气的天气数据就是从其他服务器上获取到的,并不是腾讯天气服务器上的数据。那它们是怎么做到跨域资源(数据)共享的呢?在这里学习一下 jsonp 方式实现跨域资源共享。

JSONP 实现原理

并不是页面中所有的请求都受同源政策的限制。例如外链外部 js 文件文件时是不受同源政策的限制的,这也是为什么在使用 jQuery 时,我们并不是一定要将库文件下载到项目后才能引入使用。我们也可以直接引入 jQuery 的线上文件,并不影响正常使用。它的原理是虽然此时进行了跨域(线上资源在 jQuery 的官网上),但是 script 标签的 src 属性外链的资源请求不受浏览器同源政策的影响。这也是我们使用 JSONP 实现跨域资源共享的原理。

JSONP 的基础原理如下示例代码所示(注意 A 服务器监听 3000 端口,B 服务器监听 3001 端口,下面的所有示例都在同源政策的基础上改进,所以不会再做具体说明。):

<!-- A服务器响应的页面中的代码 -->
<body><h2>A服务器的页面</h2><!-- 全局作用域下定义fn1函数(用来处理接收数据) --><script>function fn1(data) {console.log(data);}</script><!-- 利用script标签发送跨域请求 --><script src="http://localhost:3001/jsonp1"></script>
</body>

如下图是响应的结果,想必从图中也你可以猜出了服务器端的代码。

在这里插入图片描述
在这里插入图片描述

// B服务器响应路由的代码
B.get('/jsonp1', (req, res) => {res.send('fn1({name: "tkop", age: 18})');
});

结合上面的代码示例总结 JSONP 的原理:

script 标签可以向服务器发送请求,最常见的是请求 js 脚本文件,客户端接收到服务器响应的 js 脚本文件后会立即执行。但是这个请求响应过程不受同源政策的限制,即使请求头信息中的 Sec-Fetch-Site 字段值不是 same-origin(同源),即使响应头中没有 Access-Control-Allow-Origin 字段,浏览器依旧会将服务响应的数据视为 js 脚本并在客户端运行。

JSOPN 正是利用了这一点,在客户端接收到响应前提前在全局作用域下声明一个用于接收处理响应数据的函数。再利用 script 标签发送请求,此时服务器端返回的不再是一个脚本文件,而是返回该函数的调用(函数调用的字符串)。并且将客户端需要的数据作为实参传递给函数。在客户端接收到响应后会将返回结果作为 js 脚本执行(调用返回的函数),这样就达到了获取并处理数据的目的。为了更加清楚原理我们再继续看看下面的示例代码。

<!-- A服务器响应的页面中的代码 -->
<body><h2>A服务器的页面</h2><script src="http://localhost:3001/jsonp1"></script><script>console.log(data);</script>
</body>

这次我不想直接在全局定义数据处理函数了,我想直接得到一个全局变量作为响应得到的数据(这个前后端交流成本非常高,而且还有后端开发者给你定义变量。。。。这是不可能的,纯粹是为了演示原理而已)。

// B服务器响应路由的代码
B.get('/jsonp1', (req, res) => {res.send('var data = "这就是响应数据"');
})

这就是上面的客户端代码即使没有声明 data 也不会报错的原因,返回的数据就是 data 声明

JSONP 模仿 ajax 请求

上面已经介绍了 jsonp 的基本原理,可以看出这种方案实际上已经不属于 ajax 请求的范围了。但是它可以模仿 ajax 请求,使用其原理 jsonp 是怎么模仿 ajax 请求的呢?

  1. 上面示例中的请求都是在页面加载过程就发送出去的,不像 ajax 根据客户端需要去发送。为实现这一点,我们可以在只有需要发送请求时在页面动态追加 script 标签。请求会在 script 标签被追加到页面时发送

  2. 发送请求接收到响应后在页面追加的 script 标签需要删除。如果不进行删除,每发送一次请求页面就会增加一个 script 标签。但是怎么判断 script 标签已经追加到页面且请求已经发送了呢?可以监听 script 标签的 onload 事件实现

  3. 另一个是函数名的问题。服务器返回的某函数的调用时,函数名必须与客户端声明的函数名称保持一致。这增加了前后端的沟通成本,不利于高效开发。为什么不在前端将函数名作为参数传递到后端呢?后端在获取到该参数后作为函数名并返回函数调用。

客户端代码如下:

<body><h2>A服务器的页面</h2><button id="btn">向B发送ajax请求</button><!-- 提前定义全局函数fn2 --><script>function fn2(data) {console.log(data);}</script><script type="text/javascript">var btn = document.getElementById('btn');btn.onclick = function() {// 创建script标签var script = document.createElement('script');// 设置script标签的src属性script.src = 'http://localhost:3001/jsonp2?callback=fn2';// 将script标签追加到页面中document.body.appendChild(script);// 在script标签追加完成请求发送后将其删除script.onload = function() {document.body.removeChild(script);}}</script>
</body>

服务器端代码如下:

B.get('/jsonp2', (req, res) => {var callback = req.query.callback;res.send(callback + '({ name: "tkop", age: 18 })')
})

上面的代码个人亲自敲完后运行没有出错,可以发送请求和获得响应数据,并达到预期结果。

JSONP 函数封装

JSONP 也可以像 ajax 那样将发送请求处理响应的过程封装成一个函数。在需要发送请求时,只需要调用并传入指定参数即可。参考 ajax 函数的封装过程,按照思考的步骤进行 JSONP 函数的封装。

  1. 请求地址、请求参数和响应处理函数可以作为函数的参数传入。但是由于 JSONP 方式只能发送 get 方式的请求,所以也就没有请求方式判断和请求参数格式不同等问题的处理(相比较于以前 ajax 函数的封装简单了不少)

  2. 响应处理函数作为参数传入 jsonp 请求函数,理想做法是以匿名函数的方式传入。如果是在全局作用域声明一个命名函数在将其传入 jsonp 请求函数,这是 jsonp 的封装性非常低

  3. 这样又会出现两个问题。问题 1:函数必须要挂载在全局作用域内,在后端返回函数调用时才可以找到相应的函数。问题 2:在发送请求时必须将函数名作为参数传递给后端

  4. 针对以上两个问题,可以直接在函数内部封装给函数命名和将其挂载在全局作用域下的代码

  5. 但是还是需要解决一个问题,就是不能在内部为所有请求的函数起同一个函数名,这样会造成函数覆盖的问题。例如给按钮 1 和按钮 2 都绑定点击事件,点击后均是发送 jsonp 请求,如果它们的响应处理函数命名相同,那无疑都会执行后面定义的那个函数

  6. 所以需要给每个函数的命名是随机而不同的

服务器端 jsonp 函数封装的过程和发送 jsonp 请求的代码

<body><h2>A服务器的页面</h2><button id="btn">向B发送jsonp请求</button><script type="text/javascript">var btn = document.getElementById('btn');function jsonp(options) {// 处理其他参数的拼接var params = '';for (var k in options.data) {params += '&' + k + '=' + options.data[k];}// 响应处理函数var fnName = 'myJsonp' + Math.random().toString().replace('.', '');window[fnName] = options.success;// 请求完整地址var url = options.url + '?callback=' + fnName + params;// 创建script标签var script = document.createElement('script');// 设置script标签的src属性script.src = url;// 将script标签追加到页面中document.body.appendChild(script);// 在script标签追加完成请求发送后将其删除script.onload = function() {document.body.removeChild(script);window[fnName] = null;}}btn.onclick = function() {jsonp({// 地址url: 'http://localhost:3001/jsonp3',// 其他请求参数data: {name: 'tkop',age: 18},// 响应处理函数success: function(data) {console.log(data);}})}</script>
</body>

服务器端的响应代码

B.get('/jsonp3', (req, res) => {var callback = req.query.callback;var data = JSON.stringify({ name: 'TKOP', password: 123456 });var result = callback + '(' + data + ')';res.send(result)
});

对服务器端的响应代码可以做一下优化,响应对象下有一个 jsonp() 方法封装了回调函数名的获取和与数据字符串拼接为函数调用的过程。所以使用这个方法的后端实现示例代码如下:

B.get('/jsonp3', (req, res) => {res.jsonp({ name: 'TKOP', password: 123456 });
});
// 响应结果
//typeof myJsonp07452599508118731 === 'function' && myJsonp07452599508118731({"name":"TKOP","password":123456});

写完验证没有问题后,我在想:这样岂不是每发送一个请求就会在全局挂载一个全局函数吗?需不需要在接收到响应并调用处理函数后将它删了?我觉得有必要,所以处理需要删除 script 标签外我另外添加了清除全局函数的代码。

在这里插入图片描述
封装结果如上图所示,发送几次请求后没有问题,如果试一下给其他按钮绑定点击事件发送 jsonp 请求的也没有发现问题。

其他方式

实现跨域的方式还有 CORS 和 WebSocket ,但是在此记录的是利用服务器作为中间人向其他服务器请求数据的方式。因为同源政策只是浏览器为保护用户信息安全而在客户端执行的策略,而服务器是没有同源政策的。浏览器不跨域向服务器请求数据时,服务器转而向其他服务器请求数据,在得到其他服务器响应的数据后转手响应给浏览器。这也隐式实现了跨域的数据请求。原理如下:

aaa: A 哥我希望得到这些数据。
A : 这些数据在 B 上,我去跟他要吧,你自己去要的话,他不会给你。
A : B 哥我要这些数据。
B : 给你。
A :aaa 这是你要的数据。

具体的实现服务器端需要使用到第三方库(request)向另一个服务器发送请求和接收处理响应。

// aaa 页面脚本向自己的服务器发送ajax请求
ajax({type: 'get',url: 'http://localhost:3000/server',success: function (data) {console.log(data);}
})// A服务器的响应路由
app.get('/server', (req, res) => {// 向B服务器发送请求request('http://localhost:3001/cross', (err, response, body) => {res.send(body);})
});// B服务器响应路由
app.get('/cross', (req, res) => {res.send('ok')
});

有关 CORS 和 WebSocket 有空再总结。。。。

这篇关于JSONP 方式实现跨域请求数据的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/898295

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Debezium 与 Apache Kafka 的集成方式步骤详解

《Debezium与ApacheKafka的集成方式步骤详解》本文详细介绍了如何将Debezium与ApacheKafka集成,包括集成概述、步骤、注意事项等,通过KafkaConnect,D... 目录一、集成概述二、集成步骤1. 准备 Kafka 环境2. 配置 Kafka Connect3. 安装 D

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形