跨域原理(一) JSONP

跨域与同源策略:

什么是跨域?

跨域 :跨域访问问题指的是在客户端浏览器中,由于同源策略的限制,不允许从一个源直接访问另一个源的资源。当浏览器发起一个跨域请求时,会被浏览器拦截,并阻止数据的传输。

如上图,只有当协议域名端口都一致才叫同源。

同源策略 :为了保护用户的隐私和数据安全,浏览器就会通过实施同源策略 来限制不同源 之间的直接通信。但也有一些情况不受同源策略的限制,分别是:Img标签下的Link标签下的Script标签下的(至于为什么它们不受限,请往下看)。

本篇我们就来一步一步的深入剖析JSONP这种方法之所以能实现跨域的原理,以及代码实操。

JSONP实现跨域的原理?

现在我们知道了同源策略的存在,也明白了不同源之间的数据传输是受到限制的,但是神奇的一幕发生了,我居然能直接在页面上展示出一张不同源的图片!

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
     <img src="https://img2.baidu.com/it/u=446981511,947966320&fm=253&fmt=auto&app
     =120&f=JPEG?w=750&h=500" alt="">
     // 这是一张百度上随便找来的图片
</body>
</html>
js 复制代码
const http = require('http')

http.createServer(function(req, res) {
    res.end('hello world')

}).listen(3000) // 项目运行在 3000 端口

要知道,要想展示出百度的这张图片,就势必得从我们本机的端口发出请求,也就势必的会造成跨域。但正是因为img标签不受同源策略的限制,所以它才能够正常的展示出来。

其实最开始的同源策略被打造的极其严格,严格到任何资源只要是不同源的就无法请求到,给当时的第三方图片展示带来了极大的麻烦。但是在项目中加载第三方图片的需求应该属于合理的,于是同源策略在后面又修改了一下,把img标签 排除在外,也就是放入白名单了。link标签Script标签同理。

于是,利用img标签不受同源策略的限制的原理,我们用来实现JSPON的跨域。

JSPON跨域的实现

现在我们知道,直接发送ajax请求会被同源策略拦截,于是我们将ajax请求塞在src属性里面,再发送给后端,这样前端就能正常发送请求给后端,后端也能返回数据。

但是相比于正常发送 ajax 请求给后端,这种方式有一个很大的区别,那就是不能直接操作后端返回来的数据

所以我们现在就得在原有的基础上进行封装,使得我们能够操作后端返回的数据。

那么,我们第一步的思路 就是创建一个jsonp函数,每次调用这个函数就会向后端发送请求,随后就可以通过then对后端返回的响应体进行操作。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        function jsonp(url) {
            return new Promise((resolve, reject) => {
                const script = document.createElement('script'); // 创建一个script标签
                script.src = url; // 设置script标签的src属性
                document.body.appendChild(script); // 将script标签添加到页面中
            })
        }

        jsonp('http://localhost:3000').then(res => {
            console.log(res);
        })
    </script>
</body>
</html>

以上第11,12、13行的代码,先是创建一个script标签,再设置script标签的src属性,最后将script标签添加到页面中:

要想将浏览器请求到的数据为我们所用,关键在于我们接下来要向后端传递的这个我们命名为callback的参数。 我们又在以上的基础上做出修改,在设置script标签的src属性过程中将callback参数拼接上去,传递给后端。

html 复制代码
<body>
    <script>
        function jsonp(url, cb) {
            return new Promise((resolve, reject) => {
                const script = document.createElement('script');
                window[cb] = function (data) {

                }

                script.src = `${url}?cb=${cb}`; // 拼接callback参数
                document.body.appendChild(script);
            })
        }
        jsonp('http://localhost:3000', 'callback').then(res => {  //传递callback参数给后端
            console.log(res)

        })
    </script>
</body>

注意修改后的第6行,我们在全局(window)上定义了一个名为cb(callback)的一个函数体,至于它起到的作用,我们很快会讲到。

现在既然我们前端将callback参数传递给了后端,现在后端自然要接收这个参数,并且,别忘记这么做的最终目的是为了凭借这个参数,我们能将后端的数据能够返回给前端。

于是我们将后端代码修改成这样:

js 复制代码
const http = require('http')

http.createServer(function(req, res) {
    const query = new URL(req.url, `http://${req.headers.host}`).searchParams 
    // 拿到前端传递的路径和地址
    
    if(query.get('cb')) {
        const cb = query.get('cb')       // 'callback'
        const data = 'hellow world'      // 我们想向前端返回的数据
        const result = `${cb}("${data}")`  // 拼接数据:'callback('hellow world')'

        res.end(result)                  // 返回给前端
    }

    // res.end('hello world')

}).listen(3000) // 项目运行在 3000 端口

现在重点来了:

在第4行,我们通过定义query来获取前端传递的路径与地址,因此在第8行我们就能通过query.get('cb')获取到前端地址传递过来的参数callback,假设我们在第9行定义我们想要向前端返回hellow world,只需要使用${cb}(${data})将我们想要向前端返回的信息拼接上去即可!

好了,这时候后端返回callback的调用 ,我们提前在全局定义的函数体就能起作用了,现在后端返回的hellow world就是实参,window[cb] = function (data)中的data就是形参。

那么现在我们只需添加上resolve(data),14行的.then中就能得到我们想要的数据!

js 复制代码
<body>
    <script>
        function jsonp(url, cb) {
            return new Promise((resolve, reject) => {
                const script = document.createElement('script'); // 创建一个script标签
                window[cb] = function (data) { // 全局定义function callback(){}
                  resolve(data);
                }

                script.src = `${url}?cb=${cb}`; // 设置script标签的src属性
                document.body.appendChild(script); // 将script标签添加到页面中
            })
        }
        jsonp('http://localhost:3000', 'callback').then(res => {
            console.log(res)    // 输出后端返回的数据

        })
    </script>
</body>

随着"hollow world"在控制台出现的那一刻,艺术已成!实现跨域的整个过程可谓是精妙绝伦!

最后

JSONP跨域原理: 利用img标签不受同源策略的限制,于是我们就可以通过将ajax请求塞在src属性里面,再发送给后端。

实现跨域的代码过程

  1. 借助script标签的src属性不受同源策略的影响,来发送请求。
  2. 给后端携带一个参数 callback, 并在前端定义 callback 函数体。
  3. 后端返回 callback 的调用形式并将要响应的值作为 callback 的实参。
  4. 当浏览器接收到响应后, 就会触发全局的 callback 函数从而让 callback 以参数的形式 接受到后端的响应。

缺陷:需要后端配合, 只能发get请求。

用白话来说,JSONP这种方法实现跨域的代码过程就是:前端先给后端一个参数,后端再把这个参数写成调用的样子,然后再把后端想要返回给前端的数据作为实参塞到这个调用体里来,最后整体返回给前端。

相关推荐
PAK向日葵2 小时前
【算法导论】PDD 0817笔试题题解
算法·面试
加班是不可能的,除非双倍日工资4 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi5 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip5 小时前
vite和webpack打包结构控制
前端·javascript
excel5 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国6 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼6 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy6 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT6 小时前
promise & async await总结
前端
Jerry说前后端6 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化