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

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time