有意思的serviceWork,拦截请求、网络断了还能访问页面

2,780 阅读10分钟

观看文章之前看一下我们的目的,我想使用serviceWorker去拦截百度的请求,从而实现serviceWorker拦截请求的功能,是不是很有意思?

image.png

serviceWorker介绍

Service Worker 是一种 Web Worker,它可以作为浏览器和网络之间的代理服务器,拦截并处理浏览器发出的网络请求。Service Worker 可以让我们在网络请求发出前,对请求进行拦截,从而实现离线缓存、消息推送、后台数据同步等功能。Service Worker 可以在浏览器关闭后依然运行,因此可以在浏览器关闭后继续处理网络请求,这也是 Service Worker 能够实现离线缓存和消息推送的原因。
总的来说,service能做的事情就是拦截请求、离线缓存、消息推送等等功能

本文只讲拦截请求和离线缓存,消息推送调用self.registration.showNotification即可,对于关闭浏览器还能继续推送的一般用不到,有需要再写。

实现拦截百度的请求

实现拦截百度请求有几个步骤以及需要解决的问题:

  1. 注册serviceWorker的时候会出现跨域问题,这里使用nginx去解决
  2. 想要在百度嵌入我们的脚本,需要用到油猴插件(需要科学上网) 不需要科学上网的油猴插件:阿里云提取码:88xj

解决跨域问题

这个可以阅读我之前的文章前端解决跨域,nginx 反向代理,若不想看内容,可以直接安装nginx,并复制文章末尾的所有配置去替换conf/nginx.conf文件。
复制完成之后仅修改两处地方,以下两处:

image.png 修改成:

        location / {
            proxy_pass https://www.baidu.com/;
            
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

        }
        location /baidu/ {
            proxy_pass http://127.0.0.1:5504/node/webPush/;
            
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

        }
http://127.0.0.1:5504/node/webPush/  这个路径是我写serviceWorker的目录,使用vscode的goLive启动的一个服务器,
这里待会说

修改的地方一个是默认处理百度的网页的,一处是将serviceWorker文件跨域变成同域,标注以/baidu/开头的请求。
保存之后就启动nginx服务器,启动指令需要在当前目录cmd,详细可以看nginx我那篇文章,启动指令:start nginx,启动完之后在浏览器输入127.0.0.1出现以下界面代表成功:

image.png

使用右后插件注入脚本

安装完成油猴插件之后在浏览器右上角能看到这个图标

油猴插件代码

image.png 接下来我们需要再这里写新脚本注入我们的代码

image.png 以下是我们写注入的代码:

image.png

@match http://127.0.0.1/*:匹配的路径才注入,匹配以http://127.0.0.1下的所有路径,以下是全部代码,直接粘贴使用即可:

// ==UserScript==
// @name         百度拦截
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        http://127.0.0.1/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
        if ('serviceWorker' in navigator) {
            window.addEventListener('load', function () {
                navigator.serviceWorker.register('/baidu/sw.js').then(function (registration) {
                    console.log('ServiceWorker registration successful with scope: ', registration.scope);
                }, function (err) {
                    console.log('ServiceWorker registration failed: ', err);
                });
            });
        }
    // Your code here...
})();

serviceWorker代码

接下来我们需要实现一个serviceWorker里面的逻辑, 具体serviceWorker的使用可以参考MDN
编写代码之前我们需要去看一下我们拦截的对应api,不然会全部都拦截,我们只需要拦截一个关键词提示的api即可,打开百度页面的控制台,输入搜索词可以看到这里关键词提示的api是https://www.baidu.com/sugrec这个接口

image.png

接下来就好办了,我们定义一个数组urlList,匹配数组里面的api才进行拦截,以下是sw.js全部代码:

let urlList = [
  "/sugrec"
]

self.addEventListener('fetch', function (event) {
  console.log(event.request);
  if (urlList.some(item => event.request.url.includes(item))) {
    event.respondWith(
      fetch(event.request).then(function (response) {
        return response.json();
      }).then(function (data) {
        return new Response(
          JSON.stringify({
            ...data, g: [
              {
              "type": "sug",
              "sa": "s_1",
              "q": "被拦截的请求"
            },
              {
              "type": "sug",
              "sa": "s_2",
              "q": "被拦截的请求2"
            },
              {
              "type": "sug",
              "sa": "s_3",
              "q": "被拦截的请求3"
            },
              {
              "type": "sug",
              "sa": "s_4",
              "q": "被拦截的请求4"
            },
          ]
          }),
          { headers: { 'Content-Type': 'application/json' } }
        );
      })
    );
  }
});
  • self.addEventListener('fetch',function):一个事件监听器,用于监听fetch事件。当触发fetch事件时,该函数使用
  • urlList.some(item => event.request.url.includes(item)):函数检查请求的URL是否包含在

urlList数组中的任何URL中。

  • event.respondWith:是Service Worker API中的一个方法,用于拦截网络请求并返回自定义响应。
  • event.request:表示发出的请求,而event.respondWith方法则用于返回自定义响应

保存之后去浏览器输入127.0.0.1/baidu/刷新一两次就能访问被拦截之后的接口,刷新多几次是因为第一次serviceWorker注册,之后进入浏览器才生效。

注意

  • serviceWorker拦截请求使用场景并不是拦截别人的请求数据,作者是使用这种方式进行学习比较感兴趣,service真正拦截的作用其实是体现在请求数据的缓存作用
  • Service Worker 可以缓存任何类型的数据,包括 HTML、CSS、JavaScript、图片、音频、视频等等。但是,需要注意的是,Service Worker 缓存的数据必须是不变的,因为 Service Worker 可以在离线状态下使用缓存的数据,如果缓存的数据发生了变化,那么 Service Worker 就会使用旧的缓存数据,这样会导致应用程序出现错误。
  • 拦截api请求的数据常用在一些详情数据上,例如在做商品类型的项目时,商品详情一般是很少会变化的,这个时候可以使用api进行拦截请求,优先使用缓存的数据,这样做的目的可以减少服务器的请求。 接下来我也会讲一下离线缓存页面,使得网页能在无网络的情况下进行访问

离线缓存网页,没网也能进行网络访问

离线缓存页面的使用场景主要是在网络不稳定或者没有网络的情况下,用户仍然可以访问页面内容,提高用户体验。比如,在移动端应用中,用户可能会在地铁、电梯等信号不好的地方使用应用,如果应用支持离线缓存页面,用户就可以在这些场景下继续使用应用,而不会因为网络问题而无法访问页面。另外,离线缓存页面还可以减少服务器的负载,提高应用的性能。
同时也能在官网加入离线缓存,这样即使在服务器出现异常情况下仍然能继续访问

html部分的代码主要是一个axios的请求,我们需要缓存这个请求,跟注册一个serviceWorker,以下是body的代码:

<body>
    <div>1111111111111111111111111111111111</div>
    <script>
        if ('serviceWorker' in navigator) {
            window.addEventListener('load', function () {
                navigator.serviceWorker.register('./serviceWorker.js').then(function (registration) {
                    console.log('ServiceWorker registration successful with scope: ', registration.scope);
                }, function (err) {
                    console.log('ServiceWorker registration failed: ', err);
                });
            });
        }

        axios.get('http://localhost:3000/getList')
            .then(function (response) {
                console.log(response.data);
            })
            .catch(function (error) {
                console.log(error);
            });

    </script>
</body>

然后就到serviceWorker那边,代码逻辑上都很简单,以下是代码:

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('demo-cache').then(function (cache) {
      return cache.addAll([
        'demo1.html',
        'demo2.html'
      ]);
    })
  );
});

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      if (response) {
        return response;
      }
      return fetch(event.request).then(function (response) {
        // 将请求的响应缓存起来
        if(event.request.url.includes("/api/")){
          caches.open('my-cache').then(function (cache) {
            cache.put(event.request, response);
          });
        }
        return response.clone();
      });
    })
  );
});

上面那个是监听serviceWorker注册完成之后就开始创建一个缓存名为 demo-cache 的缓存,并使用 cache.addAll() 方法将两个 HTML 文件 demo1.htmldemo2.html 添加到缓存中。我这里还有个index.html文件,是没有被缓存的,用处是校验离线缓存功能;后面的就是缓存请求相关的数据。

image.png 缓存成功之后的会带有serviceWorker的标志

image.png 我们把这里调成离线模式,就会发现被缓存的页面能正常显示,不被缓存的index.html就访问不了

image.png

有人应该好奇,离线缓存使用manifest不更简单吗?

manifest离线缓存(浏览器需要https才支持,本地开发也一样)

manifest离线缓存的步骤更简单,只需要在html标签配置manifest属性

<html manifest="cache.appcache">

然后在当前目录新建一个cache.appcache指定缓存的目标就可以了,这不简单很多吗

CACHE MANIFEST
#Version 1.0

CACHE:
index.html
demo3.html

相关配置介绍如下:

CACHE:部分列出应该被缓存的资源,

NETWORK:部分列出不应该被缓存的资源,

FALLBACK:部分指定应在无法加载资源时使用的备用资源。

serviceWorker和manifest离线缓存的区别

Service Worker 和 Manifest 都可以用于实现离线缓存,但是它们的实现方式和优劣势不同。

Manifest

Manifest 是一种描述 Web 应用程序的 JSON 文件,它可以指定 Web 应用程序的图标、名称、主页 URL 等信息。Manifest 还可以指定 Web 应用程序的缓存清单,从而实现离线缓存。当用户访问 Web 应用程序时,浏览器会下载 Manifest 文件,并根据 Manifest 文件中指定的缓存清单,将 Web 应用程序的资源缓存到本地。当用户离线时,浏览器会从本地缓存中加载 Web 应用程序的资源,从而实现离线访问。但是在更新之后缓存没办法控制更新,想要更新缓存,只能通过修改manifest文件跟调用applicationCache.update()方法去更新;

Service Worker

Service Worker 是一种 Web Worker,它可以作为浏览器和网络之间的代理服务器,拦截并处理浏览器发出的网络请求。Service Worker 可以让我们在网络请求发出前,对请求进行拦截,从而实现离线缓存、消息推送、后台数据同步等功能。Service Worker 可以在浏览器关闭后依然运行,因此可以在浏览器关闭后继续处理网络请求,这也是 Service Worker 能够实现离线缓存和消息推送的原因。与 Manifest 不同,Service Worker 可以对网络请求进行拦截和处理,从而实现更加灵活的离线缓存策略。Service Worker 还可以实现消息推送、后台数据同步等功能,从而提高 Web 应用程序的用户体验。 因此,Service Worker 相对于 Manifest 来说,具有更加灵活的离线缓存策略和更加丰富的功能。但是,Service Worker 的实现相对来说更加复杂,需要开发者具备一定的技术水平。

Service Worker的优势

  • Service Worker 可以实现离线缓存的精细控制。Service Worker 可以对不同的请求进行不同的缓存策略,从而实现更加灵活的离线缓存控制。比如,可以对一些静态资源进行长时间缓存,对一些动态资源进行短时间缓存,从而提高 Web 应用程序的性能和用户体验。
  • Service Worker 可以实现消息推送。Service Worker 可以在后台接收服务器推送的消息,并在用户打开 Web 应用程序时,将消息推送给用户。这种方式可以实现实时通知和消息推送的功能,从而提高 Web 应用程序的用户体验。
  • Service Worker 可以实现后台数据同步。Service Worker 可以在后台接收服务器的数据,并将数据同步到本地数据库中。这种方式可以实现后台数据同步的功能,从而提高 Web 应用程序的用户体验。

Service Worker的劣势

  • Service Worker 的实现相对来说更加复杂,需要开发者具备一定的技术水平。
  • Service Worker 可以实现离线缓存,但需要开发者自己实现缓存策略。如果缓存策略不当,可能会导致缓存数据过期或者占用过多的存储空间,从而影响用户体验。
  • Service Worker 可能会影响 Web 应用程序的安全性。由于 Service Worker 可以拦截和处理网络请求,因此可能会被恶意攻击者利用,从而对 Web 应用程序造成安全威胁。
  • Service Worker 并不是所有浏览器都支持,特别是在移动端,一些旧版本的浏览器可能不支持。因此,在使用 Service Worker 时需要注意浏览器的兼容性问题,需要提供备选方案以保证用户体验。