第四章 拦截网络请求

本章会深入讨论 fetch 事件并展示它所提供的诸多用例。

4.1 Fetch API

传统上,从服务器获取数据是通过使用 XMLHttpRequest 对象,也成为 AJAX。

1
2
3
4
5
6
7
8
9
10
11
12
13
var request
var request
if (window.XMLHttpRequest) {
request = new XMLHttpRequest()
} else {
try {
request = new ActiveXObject('Msxml2.XMLHTTP')
} catch (e) {
try {
request = new ActiveXObject('Microsoft.XMLHTTP')
} catch (e) {}
}
}

使用 XMLHttpRequest 对象来编写代码会相当棘手,逻辑越复杂,代码就越复杂。

Fetch API 是 Service Worker 全局作用域的一部分,可以用来在任何 Service Worker 中发起 HTTP 请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fetch('/some/url', {
method: 'POST',
headers: {
'auth': '1234'
},
body: JSON.stringify({
name: 'dean',
login: 'dean123'
})
})
.then(function (data) {
console.log('Request success: ', data)
})
.catch(function (error) {
console.log('Request failure: ', error)
})

4.2 fetch 事件

在 Service Worker 中创建自定义 HTTP 响应

1
2
3
4
5
6
7
self.addEventListener('fetch', function (event) {
if (/\.jpg$/.test(event.request.url)) {
event.respondWith(
new Response('<p>This is a response that comes from your Service Worker!</p>', { header: 'Content-Type': 'text/html' })
)
}
})

Service Worker 生命周期

前文说过,只有当 Service Worker 安装完成并且用户刷新了页面或者跳转到网站的其他页面时,Service Worker 才会激活并开始拦截请求。

1
2
3
self.addEventListener('install', function (event) {
event.waitUntil(self.skipWaiting())
})

skipWaiting() 函数强制等待中的 Service Worker 成为激活的 Service Worker。该函数还可以与 self.clients.claim() 一起使用,以确保底层 Service Worker 的更新立即生效。

1
2
3
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})

4.3 fetch 实战

4.3.1 使用 WebP 图片的示例

支持 WebP 格式的浏览器会在每个 HTTP 请求中添加 accept: image/webp 请求头来告知服务端它支持 WebP 格式。现在 Service Worker 可以拦截请求,将 WebP 图片进行返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
self.addEventListener('fetch', function (event) {
if (/\.jpg$|.png$/.test(event.request.url)) {
var supportsWebp = false
if (event.request.headers.has(accept)) {
supportsWebp = event.request.headers.get('accept').includes('webp')
}

if (supportsWebp) {
var req = event.request.clone()

var returnUrl = req.url.substr(0, req.url.lastIndexOf('.')) + '.webp'

event.resondWith(
fetch(returnUrl, {
mode: 'no-cors'
})
)
}
}
})

4.3.2 使用 Save-Data 请求头的示例

使用最新版本浏览器,有一个节省流量的选项,启用该功能则会为每个 HTTP 请求添加一个新的请求头 saveData

1
2
3
4
5
6
7
8
9
self.addEventListener('fetch', function (event) {
if (event.request.headers.get('save-data')) {
// 为了节省流量限制了图标和字体
if (event.request.url.includes('fonts.googleapis.com')) {
// 不返回任何内容
event.respondWith(new Response('', { status: 417, statusText: 'Ignore fonts to save data.' }))
}
}
})