• 前端跨域解决方案: JSONP的通俗解说和实践


     对于前端开发者而言,跨域是一个绕不开的话题。只有真正明白了各种方案的工作机制,才能针对性地进行跨域方案选型。本文将以探索者的视角,试图用最通俗的语言对一种“鼎鼎大名”的跨域解决方案——JSONP的工作细节进行介绍。
     需要说明的是,JSONP并不是仅仅需要前端处理即可,它还需要后端进行适当的配合设置。为此,本文将适当插入少量的node.js代码(koa框架),以便更直观的展现jsonp的工作原理。

    问题引入:同源策略

    什么是同源?

    文档的来源相同,即协议、主机及端口均相同。
    

    假设有一台域名是http://www.aaa.com的服务器,它的根目录存放有index.html文档,根目录下的js文件夹下存放有main.js文件。即两个文档的路径分别为:

    http://www.aaa.com/index.html
    
    http://www.aaa.com/js/main.js
    

    这时,两个文档的协议、主机、端口均相同,均为:

    协议:http
    主机:www.aaa.com
    端口:80
    

    因为这里没有显示设置通信端口,因此默认是80,显然两者是同源的。假设将其中一个文档的通信协议换为https,或者显示指定通信端口为10032,或者放到另外一台服务器www.bbb.com,这时两个文档都将变得不再同源。

    本地文件的跨域问题

    我们在本地新建一个test.html文件,使用浏览器打开,这时浏览器显示了该文档的url地址:

    file:///C:/Users/lsz/Desktop/demo/test.html
    
    协议:file
    主机:本机
    端口:不详,未查询到相关资料
    

    显然,这时test.html与前面两个文档采用的通信协议不同,所在的主机也不同,必然不属于同源文档。因而,当test.html文档视图访问前述两个文档时,会引发跨域问题。这也就是我们在本地随意新建一个文档,不做跨域处理时,无法从接口服务器获取数据的原因。

    同源策略是客户端还是服务端的限制

    它是客户端,即浏览器的限制。浏览器限制JS脚本不能读取不同源的文档。

    为什么要设置同源策略

    同源策略的限制,使得JS脚本不能读取不同源文档,可以防止脚本窃取其他页面的用户输入,从根本上是为了安全性。

    为什么又需要跨域处理呢

    • 同源策略虽然提高了安全性,但也会使得合法的请求也被拦截在外,这将使得我们的前端开发难以进行。
    • 因此,我们需要寻求合适的解决方案来正常获取服务器数据,当然并不是要去改变浏览器本身的同源策略。

    JSONP是如何解决跨域的

    为什么叫JSONP

    JSONP中文可以理解成填充式JSON,P的英文是padding,填充之意,也就是经过处理后的JSON。
    那么,经过处理后的JSON与原JSON有何不同呢?
    看这样一段JSON数据:

    [1,2,{"buckle":"my shoe"}]
    

    如果使用JSONP作为跨域技术,那么服务器将不会直接返回以上JSON格式数据,而是会将其包裹,成为如下格式:

    handleResponse([1,2,{"buckle":"my shoe"}])
    

    在原先的基础上,加上了一对圆括号,并外面套上了一个函数名,成为新的JSON字符串。

    JSONP是如何绕开同源策略的限制的

    • JSONP是通过<script>元素绕开同源限制的。
    • <script>元素不受同源策略的影响。

    我们都知道,当给script的src属性指定特定的url地址,事实上,将会加载执行该url对应的JavaScript代码。
    假设我们的服务器接口地址是http://www.aaa.com:3000/hello/word,该接口地址的响应是handleResponse(‘Hello, world!’),这个响应是一个字符串。正常情况下,在浏览器地址栏直接输入http://www.aaa.com:3000/hello/word,将会看到浏览器中显示handleResponse(‘Hello, world!’)的字符串。
    现在,我们在本地新建一个test.html文件,并增加一个script标签,给其src属性指定为上述地址,即:

        <script src="http://www.aaa.com:3000/hello/world"></script>
    

    创建一个处理响应的handleResponse函数:

        <script>
            function handleResponse(resp){
                alert(resp);
            }
        </script>
    

    完整示例:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <script>
            function handleResponse(resp){
                alert(resp);
            }
        </script>
        <script src="http://www.aaa.com:3000/hello/world"></script>
    </body>
    </html>
    

    现在,在浏览器中打开本地文档test.html,将会看到,弹出一个窗口,窗口中显示hello,word!。这表明我们的跨域已经成功!

    以上的改进:JSONP通常动态创建script元素,前端指定函数名

    在上面的案例中,存在两个问题:

    • 对应于每一个服务端接口地址,都需要在html中写定一个script标签,指定其src属性,非常不灵活。能否不写定script标签,直接在JS代码中接收响应呢?
    • 前端的处理函数handleResponse已经由服务端完全指定,想要接收服务端的数据,就必须编写名称为handleResponse的函数。那么是否能由前端自行命名响应处理函数呢?

    答案是肯定的。
    (一)js生成script标签

    我们只需要在js代码中创建一个script标签,并设置其src属性即可。具体示例可见后面的代码。

    (二)前端指定函数名

    我们只需在前端请求中附加查询参数,在查询参数中指定处理函数名称,服务端接收查询参数,返回处理函数名称包裹的jsonp数据即可。koa服务器代码示例如下:

    const Koa = require('koa');
    const Router = require('koa-router');
    
    const app = new Koa();
    const router = new Router();
    
    router.get('/',async ctx =>{
        const cb = ctx.query.callback;
        ctx.status = 200;
        ctx.body = `${cb}({ msg: '您正在访问koa服务器根目录!'})`;
    });
    
    app.use(router.routes()).use(router.allowedMethods());
    app.listen(3000,() => {
    });
    

    以上代码中,当前端访问服务器根目录时,服务器接收前端传递的callback查询参数,并将返回前端指定的函数名包裹的json字符串。
    现在,本地文件test.html中前端代码如下:

        <script>
            var script = document.createElement('script');
            script.type = 'text/javascript';
        
            // 传参并指定回调执行函数为onBack
            script.src = 'http://localhost:3000?callback=onBack';
            document.head.appendChild(script);
        
            // 回调执行函数
            function onBack(res) {
                alert(JSON.stringify(res));
            }
        </script>
    

    如果我们的前端要改变处理响应的函数名,例如将onBack改为handleRes,只需做如下处理即可,而后端无需改动:

        <script>
            var script = document.createElement('script');
            script.type = 'text/javascript';
        
            // 传参并指定回调执行函数为onBack
            script.src = 'http://localhost:3000?callback=handleRes';
            document.head.appendChild(script);
        
            // 回调执行函数
            function handleRes(res) {
                alert(JSON.stringify(res));
            }
        </script>
    
  • 相关阅读:
    HDU 1114 Piggy-Bank
    HDU 2955 Robberies
    NTOJ 290 动物统计(加强版)
    POJ 3624 Charm Bracelet
    HDU 2602 Bone Collector
    POJ 1523 SPF(无向图割顶)
    HDU 5311 Hidden String
    HDU 1421 搬寝室
    HDU 1058 Humble Numbers
    POJ 3259 Wormholes(spfa判负环)
  • 原文地址:https://www.cnblogs.com/twodog/p/12134767.html
Copyright © 2020-2023  润新知