如何用 service worker 增加响应体的 header 字段

本文介绍了在前端用 service worker 增加 header 字段。不用配置 nginx, 就可以实现CORP(跨源隔离)。

关键词

service worker、proxy、 拦截、fetch、 CORP

问题背景

之前实现了 web 端视频编辑的功能(详见(如何用 ffmpeg + canvas 写一个在线可视化音视频编辑工具),用了@ffmpeg/ffmpeg库。 然后遇到一个问题,ffmpeg 使用到了 SharedArrayBuffer, 但是 SharedArrayBuffer 由于安全问题,会有一些限制。如果要在页面中使用 SharedArrayBuffer,需要设置页面为跨域隔离。开启方式为在页面中设置两个请求头,可以设置 CORP

http 复制代码
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin

本来项目的静态页面是部署在 Github 的,但是 Github 无法设置 header,所以只能将网页部署在 vercel 上。 在进行文章的分享后,有大佬在评论区提供了一个新的解决方案,来让 github 就可以访问。方案就是通过 service worker 进行 fetch的拦截, 来实现改写 header 的目的,这确实是一个非常好的方案。

service worker

首先来简单的介绍一下 service worker

概念

Service Worker是一个强大的技术,它为我们提供了在浏览器中运行后台脚本的能力。通过使用Service Worker,我们可以实现离线缓存、推送通知和拦截网络请求等功能,为用户提供更好的体验。 其中,拦截网络请求并修改请求头是Service Worker的一个重要应用场景,它使我们能够动态地控制和定制请求的行为。

修改请求头

我们来看如何实现 header 的拦截,分为两个步骤:

  • 注册 service worker
  • 劫持 fetch, 修改 header

首先我们需要注册 service worker

js 复制代码
// 注册Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(function(registration) {
      console.log('Service Worker 注册成功:', registration);
    })
    .catch(function(error) {
      console.log('Service Worker 注册失败:', error);
    });
}

然后我们在fetch事件中拦截了请求。在拦截的处理函数中,我们使用fetch函数来获取原始的响应对象。然后,我们克隆了响应对象,并使用Headers对象来修改请求头,添加了一个自定义的请求头字段。最后,我们使用修改后的请求头创建了一个新的响应对象,并将其返回。

javascript 复制代码
// service-worker.js

// 拦截请求并修改header
self.addEventListener('fetch', function(event) {
  event.respondWith(
    fetch(event.request)
      .then(function(response) {
        // 克隆响应对象
        const clonedResponse = response.clone();
        
        // 修改请求头
        const modifiedHeaders = new Headers(clonedResponse.headers);
        modifiedHeaders.set('Custom-Header', 'Custom Value');
        
        // 创建新的响应对象,将修改后的请求头应用于其中
        const modifiedResponse = new Response(clonedResponse.body, {
          status: clonedResponse.status,
          statusText: clonedResponse.statusText,
          headers: modifiedHeaders
        });
        
        return modifiedResponse;
      })
      .catch(function(error) {
        console.log('请求失败:', error);
      })
  );
});

打开浏览器的控制台,可以看到响应请求已经增加了预期的 header 字段。

按照这个思路,我们只需要在 header 中增加CORP 需要的三个字段就可以了。

js 复制代码
 // 修改请求头
const modifiedHeaders = new Headers(clonedResponse.headers);
modifiedHeaders.set('Cross-Origin-Opener-Policy', 'same-origin');
modifiedHeaders.set('Cross-Origin-Embedder-Policy', 'require-corp');
modifiedHeaders.set('Cross-Origin-Resource-Policy', 'cross-origin');

这样子,我们的页面功能就正常了。

现成的轮子

在分析完原理以后,我们继续在 github 中搜索,发现别人也有同样的问题,找到了一些现成的轮子。所以觉得不再自己实现了。

mini-coi 这个包的使用很简单,先下载一个 service worker 文件,然后在 html 文件中引入就好了, 基本不需要配置。 看一下它的源码,实现方式也基本一致,是对 fetch 做了劫持,只是做了一些细节的处理,比如不对错误请求做处理。

js 复制代码
addEventListener('fetch', e => {
      const { request: r } = e;
      if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return;
      e.respondWith(fetch(r).then(r => {
        const { body, status, statusText } = r;
        if (!status || status > 399) return r;
        const h = new Headers(r.headers);
        h.set('Cross-Origin-Opener-Policy', 'same-origin');
        h.set('Cross-Origin-Embedder-Policy', 'require-corp');
        h.set('Cross-Origin-Resource-Policy', 'cross-origin');
        return new Response(body, { status, statusText, headers: h });
      }));
    });

sw 的其他应用场景

继续搜索 Github ,还找到了很多其他有意思的场景。

设置请求超时

Service-Workers-Fetch-Timeout 监听 fetch 请求,如果请求时间超过 2s, 则会返回页面的请求是一个超时请求。

js 复制代码
function timeout(delay) {
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
          resolve(new Response('', {
              status: 408,
              statusText: 'Request timed out.'
          }));
        }, delay);
    });
}

self.addEventListener('fetch', function(event) {
    // Only fetch JavaScript files for now
    if (/\.js$/.test(event.request.url)) {
      event.respondWith(Promise.race([timeout(2000), fetch(event.request.url)]));
    } else {
      event.respondWith(fetch(event.request));
    }
});

代码压缩

service-worker-brotli 通过 Service Worker,在尚不支持BCD的CDN上启用 Brotli(一种高级压缩算法,类似 gzip)。

js 复制代码
self.addEventListener("fetch", (event) => {
    const { request } = event;
    const url = new URL(request.url);

    if (!url.protocol.startsWith("http")) return;
    if (request.method !== "GET") return;
    if (request.mode === "websocket") return;

    event.respondWith(handle_fetch(request));
});

参考

mini-coi

Service-Workers-Fetch-Timeout

service-worker-brotli

前端性能优化(二):资源优化

相关推荐
尾善爱看海2 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
驳是3 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea3 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持3 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者3 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
Csvn4 小时前
TypeScript 大型项目架构:从单体 tsconfig 到分层可扩展的工程
前端
计算机魔术师6 小时前
Dario Amodei 发文呼吁为前沿 AI 降速并提出三点计划
前端
计算机魔术师6 小时前
OpenAI的AI代理偷偷给RubyGems下毒,我们却毫无察觉
前端
甲维斯6 小时前
0代码,0建模,3句话开发一个3D游戏!
前端·游戏·游戏开发
IT_陈寒6 小时前
Vue的响应式更新把我坑惨了,原来问题出在这
前端·人工智能·后端