首页 手动实现 JSONP
文章
取消

手动实现 JSONP

1. Ajax

Ajax 技术能够像服务器请求额外的数据而无须卸载页面,会带来更好的用户体验。

Ajax 的核心是 XMLHttpRequest 对象(简称 XHR)。IE7+、Firefox、Opera、Chrome 和 Safari 都支持原生的 XHR 对象。

但是通过 XHR 实现 Ajax 通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR 对象只能访问与包含它的页面位于同一个域中的资源。

比如下面的例子中,就无法实现在 html 文件中通过 XHR 向 http://localhost:8080 请求资源。

  • 1) html 文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    <!-- 省略 -->
    <body>
      <script>
        var xhr = new XMLHttpRequest();
        xhr.onReadyStatechange = function () {
          var readyState = xhr.readyState;
          if (readyState === 4) {
            // 4:已经接收到全部响应数据
            var status = xhr.status;
            if ((status >= 200 && status < 300) || status === 304) {
              // 请求成功
              console.log(xhr.responseText);
            }
          }
        }
        xhr.open('get', 'http://localhost:8080', true);
        xhr.send(null);
      </script>
    </body>
    <!-- 省略 -->
    
  • 2) 后端用 Node.js 完成的服务器代码 server.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    const http = require('http');
    // 创建一个 HTTP 服务器
    const srv = http.createServer((req, res) => {
      const resArray = [{ name: 'nie1' }, { name: 'nie2' }];// 需要返回的数据
      const resJson = JSON.stringify(resArray);// 对象转换为JSON
      res.writeHead(200, { 'Content-Type': 'text-plain' });// 发送一个响应头给请求。
      res.write(resJson);// 发送请求主体的一个数据块
      res.end();// 结束发送请求
    });
    // 开启 HTTP 服务器监听连接
    srv.listen(8080, '127.0.0.1', () => {
      const address = srv.address().address;
      const port = srv.address().port;
      console.log('http://%s:%s', address, port);
    })
    

运行 node server.js 启动服务器后,在 vscode 中用浏览器打开 html 文件,会收到提示:

1
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

2. 跨域资源共享

CORS(Cross-Origin Resource Sharing,跨域资源共享) 定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。其背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

当我们发出破坏跨域安全策略的请求时,浏览器会自动添加Origin头部,所以我们只需要修改服务端代码,在writeHeader前添加代码设置头部Access-Control-Allow-Origin

1
res.setHeader('Access-Control-Allow-Origin', '*');// 跨域(*表示任何源信息,即表示此资源为公共资源)

这样我们就能在浏览器控制台中看到返回的 JSON 数据:[{"name":"nie1"},{"name":"nie2"}]

3. JSONP

JSONP 是 JSON with padding (填充式 JSON 或参数式 JSON) 的简写,是应用 JSON 的一种新方法,在后来的 Web 服务中非常流行。

JSONP 看起来和 JSON 差不多,只不过是被包含在函数调用中的 JSON:

1
callback([{ name: 'nie1' }, { name: 'nie2' }]);

JSONP 由两部分组成:回调函数和数据。

  • 1) 回调函数是当响应到来时应该在页面中调用的函数。回调函数一般是在请求中指定的,如:
    1
    
    script.src = `http://localhost:8080/?callback=handleResponse`;
    
  • 2) 数据就是传入回调函数中的 JSON 数据。

JSONP 是通过动态 <script> 元素来实现的,因为 <script> 元素和 <img> 元素类似,都有能力不受限制地从其它域加载资源。

  • 1) 先修改后端代码,根据 url 中是否存在 callback 参数,来判断返回 JSON 还是 JSONP。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    const http = require('http');
    const url = require('url');
    // 创建一个 HTTP 服务器
    const srv = http.createServer((req, res) => {
      const resArray = [{ name: 'nie1' }, { name: 'nie2' }];// 需要返回的数据
      const resJson = JSON.stringify(resArray);// 对象转换为JSON
      // res.setHeader('Access-Control-Allow-Origin', '*');// 跨域(*表示任何源信息,即表示此资源为公共资源)
      res.writeHead(200, { 'Content-Type': 'text-plain' });// 发送一个响应头给请求。
      let query = url.parse(req.url, true).query;// 参数true: 返回的url是对象。
      if (query && query.callback) {
        // 有 callback 参数,返回 JSONP
        res.write(query.callback + '(' + resJson + ')');// 发送请求主体的一个数据块
      } else {
        // 无 callback 参数,直接返回 JSON 字符串
        res.write(resJson);// 发送请求主体的一个数据块
      }
      res.end();// 结束发送请求
    });
    // 开启 HTTP 服务器监听连接
    srv.listen(8080, '127.0.0.1', () => {
      const address = srv.address().address;
      const port = srv.address().port;
      console.log('http://%s:%s', address, port);
    })
    

    修改完成后,运行 node server 启动服务。然后在浏览器中访问此服务器:

    • 输入http://localhost:8080/?callback=handleResponse:
      1
      
        handleResponse([{"name": "nie1"},{"name": "nie2"}])
      
    • 输入http://localhost:8080:
      1
      
        [{"name": "nie1"},{"name": "nie2"}]
      
  • 2) 修改 html 文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    <!-- 省略 -->
    <body>
      <script>
        function handleResponse(res) {
          console.log(res);
        }
        const doc = document;
        const script = doc.createElement('script');
        script.src = 'http://localhost:8080/?callback=handleResponse';
        doc.body.insertBefore(script, doc.body.firstChild);
      </script>
    </body>
    <!-- 省略 -->
    

    修改完成后,在 vscode 中用浏览器打开此 HTML 文件,可以在控制台中看到输出的数组:

    1
    
    [{"name": "nie1"},{"name": "nie2"}]
    
本文由作者按照 CC BY 4.0 进行授权