JSONP 解决跨域问题

目前有三种最常见的解决方案:

  • CORS,在服务器端设置几个响应头,如 Access-Control-Allow-Origin: *
  • Reverse Proxy,在 nginx/traefik/haproxy 等反向代理服务器中设置为同一域名
  • JSONP

1. 一个正常的请求: JSON

正常发请求时,curl 示例:

$ curl https://shanyue.tech/api/user?id=100
 
{
  "id": 100,
  "name": "shanyue",
  "wechat": "xxxxx",
  "phone": "183xxxxxxxx"
}

使用 fetch 发送请求,示例:

const data = await fetch("https://shanyue.tech/api/user?id=100", {
  headers: {
    "content-type": "application/json",
  },
  method: "GET",
}).then((res) => res.json());

请求数据后,使用一个函数来处理数据

handleData(data);

一个 JSONP 请求

JSONP,全称 JSON with Padding,为了解决跨域的问题而出现。虽然它只能处理 GET 跨域,虽然现在基本上都使用 CORS 跨域,但仍然要知道它,毕竟面试会问

JSONP 基于两个原理:

  1. 动态创建 script,使用 script.src 加载请求跨过跨域
  2. script.src 加载的脚本内容为 JSONP: 即 PADDING(JSON) 格式

从上可知,使用 JSONP 跨域同样需要服务端的支持。curl 示例

$ curl https://shanyue.tech/api/user?id=100&callback=padding
 
padding({
  "id": 100,
  "name": "shanyue",
  "wechat": "xxxxx",
  "phone": "183xxxxxxxx"
})

对于正常的请求有何不同一目了然: 多了一个 callback=padding, 并且响应数据被 padding 包围,这就是 JSONP

那请求数据后,如何处理数据呢?此时的 padding 就是处理数据的函数。我们只需要在前端实现定义好 padding 函数即可

window.padding = handleData;

基于以上两个原理,这里实现一个简单 jsonp 函数:

<script type="module">
    function stringify(data) {
      const pairs = Object.entries(data);
      const qs = pairs
        .map(([k, v]) => {
          let noValue = false;
          if (v === null || v === undefined || typeof v === "object") {
            noValue = true;
          }
          return `${encodeURIComponent(k)}=${noValue ? "" : encodeURIComponent(v)}`;
        })
        .join("&");
      return qs;
    }
    const fetchJSONP = ({ url, onData, params }) => {
      const callbackName = 'jsonpCallback';

      const script = document.createElement('script');
      script.src = `${url}?${stringify({ callback: callbackName, ...params })}`;
      script.onerror = (e) => {
        delete window[callbackName];
        document.body.removeChild(script);
      };
      window[callbackName] = (result) => {
        console.log(result);
        // 清理 callback
        delete window[callbackName];
        document.body.removeChild(script);
      };
      document.body.appendChild(script);
    };
    // 发送 JSONP 请求
    fetchJSONP({
      url: "http://localhost:10010",
      params: { id: 10000 },
      onData(data) {
        console.log("Data:", data);
      },
    });
  </script>

服务器端代码

JSONP 需要服务端进行配合,返回 JSON With Padding 数据,代码如下:

// 简单的 JSONP 服务端示例
// 用法:GET http://localhost:3001/jsonp?callback=xxx
const http = require('http');
const url = require('url');
const qs = require("querystring");

const server = http.createServer((req, res) => {
  const { pathname, query } = url.parse(req.url);
  const params = qs.parse(query);
    const callback = params.callback;
    const data = { message: 'Hello from JSONP server!', time: Date.now(), ...params };
    res.writeHead(200, { 'Content-Type': 'application/javascript' });
    res.end(`${callback}(${JSON.stringify(data)})`);

});

server.listen(3002, () => {
  console.log('JSONP server running at http://localhost:3002/jsonp');
});

返回结果


image.png

5. 总结 步骤 发生了什么

  1. 客户端创建 <script> 标签,请求跨域接口,并带上 callback=handleResponse
  2. 服务端接收请求,读取 callback 参数值为 handleResponse
  3. 服务端构造响应内容为:handleResponse({...}) 的字符串
  4. 浏览器加载并执行该脚本,相当于调用 handleResponse(data)
  5. 客户端处理数据,完成跨域通信
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 个人认为, 网上有很多关于jsonp的讲解, 但实在是讲得不够通俗, 所以我想写这篇文章能够让更多人接受这简单易懂...
    defaultCoder阅读 3,157评论 0 3
  • 首先,需要明确记住的是,jsonp不是ajax的一种特例,而是使用动态script来获取数据的一种方式。 原理 由...
    叠搭宝箱阅读 4,117评论 0 0
  • jsonp是跨域问题的一种解决方案,是一种常用的跨域手段,只支持JS脚本和JSON格式的数据本质是利用同源策略的漏...
    MoicA阅读 7,089评论 0 0
  • 由于浏览器的同源策略,不同协议、域名、端口的网页是不允许互相请求资源的。但在前后端分离的开发模式中,前后端的域名是...
    如光凌清尘阅读 1,781评论 0 0
  • 实战情景:A站点为HTTP 域名站点;其中业务逻辑需要访问第三方站点B,B站点是HTTPS域名(没有透露站点的IP...
    YANG_LIVE阅读 1,527评论 0 0