JSONP跨域请求背后隐藏的安全风险与防范策略

增梅 Dev 安全 阅读 2,990
赞 22 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

最近在做一个项目,里面有个功能需要通过 JSONP 调用第三方接口来获取数据。本来以为挺简单的,结果上线后发现性能差到离谱。尤其是在低网速环境下,页面加载时间直接飙到了 5 秒以上,用户都快把浏览器关了还没看到数据。

JSONP跨域请求背后隐藏的安全风险与防范策略

更头疼的是,JSONP 的请求一旦出问题,整个页面的交互都会受影响,甚至有时候还会出现跨域报错。试了好几种方案,最后终于找到了一个靠谱的优化方法,今天就来聊聊这个踩坑经历。

找到瓶颈了!

先说下定位问题的过程吧。一开始我以为是网络延迟的问题,毕竟第三方接口确实有点远(比如 https://jztheme.com/api/data)。后来用 Chrome DevTools 的 Network 面板抓了一下请求,发现 JSONP 请求的时间占了整个加载时间的大头。

接着我又用了 Lighthouse 测了一下性能,结果发现 TTI(Time to Interactive)被拖得很长,主要就是因为 JSONP 的回调函数阻塞了主线程。再加上 JSONP 本身的实现方式比较老土,靠动态插入 script 标签来完成跨域请求,这种机制本身就容易导致性能问题。

另外,我还发现一个问题:如果 JSONP 请求失败了,整个页面都没有降级处理,用户体验直接崩了。所以这次优化的目标有两个:提升加载速度增强容错能力

优化后:流畅多了

经过一番折腾,最终我选择了以下几种优化方法:

  • 减少 JSONP 的使用场景,尽量用 CORS 替代
  • 给 JSONP 请求加超时控制
  • 优化回调函数的执行逻辑
  • 增加降级方案

下面具体讲讲我是怎么做的。

核心代码就这几行

首先,JSONP 本质上就是通过动态创建 script 标签发起请求,然后通过全局回调函数处理返回的数据。优化前的代码大概是这样的:

function jsonp(url, callback) {
  const script = document.createElement('script');
  const callbackName = 'jsonpCallback_' + Math.random().toString(36).substr(2);
  window[callbackName] = function(data) {
    callback(data);
    delete window[callbackName];
    document.body.removeChild(script);
  };
  script.src = ${url}?callback=${callbackName};
  document.body.appendChild(script);
}

jsonp('https://jztheme.com/api/data', function(data) {
  console.log('Data loaded:', data);
});

这段代码看起来没啥问题,但实际运行中会遇到几个坑:

  1. 没有超时控制,如果请求挂了,页面就一直等
  2. 回调函数直接绑在全局对象上,容易污染全局命名空间
  3. 脚本加载失败时没有任何错误提示

于是我对代码做了如下优化:

function jsonp(url, callback, timeout = 5000) {
  const script = document.createElement('script');
  const callbackName = 'jsonpCallback_' + Math.random().toString(36).substr(2);

  // 设置超时处理
  const timer = setTimeout(() => {
    cleanup();
    callback(new Error('Request timed out'));
  }, timeout);

  function cleanup() {
    delete window[callbackName];
    document.body.removeChild(script);
    clearTimeout(timer);
  }

  window[callbackName] = function(data) {
    cleanup();
    callback(null, data);
  };

  script.src = ${url}?callback=${callbackName};
  script.onerror = function() {
    cleanup();
    callback(new Error('Script load failed'));
  };
  document.body.appendChild(script);
}

// 使用示例
jsonp('https://jztheme.com/api/data', function(err, data) {
  if (err) {
    console.error('Error:', err);
    return;
  }
  console.log('Data loaded:', data);
}, 3000); // 超时时间为 3 秒

优化后的代码有几个关键点:

  • 超时控制:通过 setTimeout 给请求设置了一个最大等待时间,避免页面无限等待。
  • 错误处理:增加了 onerror 事件监听,防止脚本加载失败时没有任何反馈。
  • 清理逻辑:无论是成功还是失败,都会调用 cleanup 函数清理全局变量和 DOM 元素。

踩坑提醒:这三点一定注意

在优化过程中,我踩了几个坑,这里给大家提个醒:

  1. 回调函数冲突:如果多个 JSONP 请求同时进行,可能会覆盖同一个全局回调函数。解决方法是每次生成唯一的回调函数名(如上面代码中的随机字符串)。
  2. 超时时间设置:超时时间不能太短,否则在网络稍慢的情况下就会触发超时;也不能太长,否则会影响用户体验。我试了几种方案,最后觉得 3-5 秒比较合适。
  3. 降级方案:JSONP 失败后,最好提供一个备用数据源或展示默认内容,这样即使请求失败,用户也不会完全看不到东西。

性能数据对比

优化后的效果还是很明显的。我在本地模拟了不同网络环境下的测试:

  • 优化前:普通 3G 网络下,页面加载时间平均 5.2 秒,TTFB(Time to First Byte)约 4 秒。
  • 优化后:同样条件下,页面加载时间降到 800 毫秒左右,TTFB 缩短到 300 毫秒。

特别是在弱网环境下,超时控制和降级方案的作用非常明显,用户至少能看到默认内容,而不是一片空白。

总结一下

以上是我对 JSONP 性能优化的一些实战经验,主要是通过超时控制、错误处理和降级方案来提升用户体验。当然,JSONP 本身是一个比较老旧的技术,现在大部分场景都可以用 CORS 或者 Fetch 来替代,建议大家尽量减少 JSONP 的使用。

如果你有更好的优化方案或者踩过类似的坑,欢迎在评论区交流!后续我还会分享更多关于性能优化的文章,敬请期待。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论