zoukankan      html  css  js  c++  java
  • 前端跨域解决方案: 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>
    
  • 相关阅读:
    SPLAY,LCT学习笔记(五)
    SPLAY,LCT学习笔记(四)
    SPLAY,LCT学习笔记(三)
    NOI 2012 随机数生成器
    SPLAY,LCT学习笔记(二)
    SPLAY,LCT学习笔记(一)
    bzoj 1503 郁闷的出纳员
    bzoj 1112 poi 2008 砖块
    bzoj 1064 noi2008 假面舞会题解
    数论知识点总结(noip范围)
  • 原文地址:https://www.cnblogs.com/twodog/p/12134767.html
Copyright © 2011-2022 走看看