HTML5-Service Worker實現離線頁面訪問

如果提到HTML5的新API,WebSocketWeb Workers大家應該比較熟悉。WebSocket是用於簡述請求數量的新協議,Web Workers是用於實現瀏覽器的多線程。而今天要介紹的Service Worker是用於頁面離線緩存,提供類似App的服務。注意,這和瀏覽器緩存不是一回事。

下面所有代碼請查看github下載完整版本

1. Service Worker 介紹

試想,當你正在訪問一個人的博客目錄,當你找到你感興趣的博客時候,想點擊進入查看完整博客,這時候斷網了,你將會看到如下頁面:
這裏寫圖片描述
忽略上面的網址,這是我在自己瀏覽器裏使用chrome -> 開發者工具 -> New Work -> offline 模擬斷網,訪問我本地服務器上網頁的結果。你看到頁面應該類似。這時候僅僅依靠瀏覽器緩存是無法解決問題的。於是HTML5提出了 Service Worker,它是一段運行在瀏覽器後臺進程裏的腳本,它獨立於當前頁面,提供了那些不需要與web頁面交互的功能在網頁背後悄悄執行的能力。在將來,基於它可以實現消息推送,靜默更新以及地理圍欄等服務,但是目前它首先要具備的功能是攔截和處理網絡請求,包括可編程的響應緩存管理。所以,使用它可以斷網情況下輕鬆實現攔截用戶請求,用已經緩存的頁面代替服務器響應,簡稱離線緩存。
注意:Service Worker由於權限很高,只支持https協議或者localhost

2. Service Worker使用

2.1 生命週期

先來看一下一個service worker的運行週期(圖片來源:http://www.html5rocks.com/en/tutorials/service-worker/introduction/
這裏寫圖片描述
Service worker擁有一個完全獨立於Web頁面的生命週期。

要讓一個service worker在你的網站上生效,你需要先在你的網頁中註冊它。註冊一個service worker之後,瀏覽器會在後臺默默啓動一個service worker的安裝過程。

在安裝過程中,瀏覽器會加載並緩存一些靜態資源。如果所有的文件被緩存成功,service worker就安裝成功了。如果有任何文件加載或緩存失敗,那麼安裝過程就會失敗,service worker就不能被激活(也即沒能安裝成功)。如果發生這樣的問題,別擔心,它會在下次再嘗試安裝。

當安裝完成後,service worker的下一步是激活,在這一階段,你還可以升級一個service worker的版本,具體內容我們會在後面講到。

在激活之後,service worker將接管所有在自己管轄域範圍內的頁面,但是如果一個頁面是剛剛註冊了service worker,那麼它這一次不會被接管,到下一次加載頁面的時候,service worker纔會生效。

當service worker接管了頁面之後,它可能有兩種狀態:要麼被終止以節省內存,要麼會處理fetch和message事件,這兩個事件分別產生於一個網絡請求出現或者頁面上發送了一個消息。

總結起來Service Worker的生命週期有如下幾個關鍵步驟(就是常常需要監聽並制定回調函數的事件):
1. 安裝
2. 激活,激活成功之後,打開chrome://inspect/#service-workers可以查看到當前運行的service worker
3. 監聽fetch和message事件,下面兩種事件會進行簡要描述
4. 銷燬,是否銷燬由瀏覽器決定,如果一個service worker長期不使用或者機器內存有限,則可能會銷燬這個worker
下面具體介紹這幾個事件。

2.2 生命週期中常需監聽的幾個事件

fetch事件

在頁面發起http請求時,service worker可以通過fetch事件攔截請求,並且給出自己的響應。
w3c提供了一個新的fetch api,用於取代XMLHttpRequest,與XMLHttpRequest最大不同有兩點:

  1. fetch()方法返回的是Promise對象,通過then方法進行連續調用,減少嵌套。ES6的Promise在成爲標準之後,會越來越方便開發人員。

  2. 提供了Request、Response對象,如果做過後端開發,對Request、Response應該比較熟悉。前端要發起請求可以通過url發起,也可以使用Request對象發起,而且Request可以複用。但是Response用在哪裏呢?在service worker出現之前,前端確實不會自己給自己發消息,但是有了service worker,就可以在攔截請求之後根據需要發回自己的響應,對頁面而言,這個普通的請求結果並沒有區別,這是Response的一處應用。

message事件

頁面和serviceWorker之間可以通過posetMessage()方法發送消息,發送的消息可以通過message事件接收到。

這是一個雙向的過程,頁面可以發消息給service worker,service worker也可以發送消息給頁面,由於這個特性,可以將service worker作爲中間紐帶,使得一個域名或者子域名下的多個頁面可以自由通信。

install事件

當頁面加載時觸發該事件。常用於緩存離線頁面,當斷開網絡時,在該事件中緩存的頁面將被返回給用戶。

acrive事件

當Service Worker被觸發時觸發該事件。代碼更新後,通常需要在activate的callback中執行一個管理cache的操作。因爲你會需要清除掉之前舊的數據。我們在activate而不是install的時候執行這個操作是因爲如果我們在install的時候立馬執行它,那麼依然在運行的舊版本的數據就壞了。

3. Service Worker實例

再次提醒:下面所有代碼請查看github下載完整版本

例子很簡單,當我在連接網絡時訪問頁面,結果如下:
這裏寫圖片描述
控制檯如下:
這裏寫圖片描述
這裏寫圖片描述
解釋下這裏的 scope,是指可以攔截請求的域。
當我在chrome裏使用chrome -> 開發者工具 -> New Work -> offline 模擬斷網,刷新頁面:
這裏寫圖片描述
同一個網址,返回了不同頁面。說明Service Worker成功攔截了原始請求(如果不攔截,會像上面那樣出現頁面無法訪問的提示)。

主要代碼及註釋如下:
index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>test service worker - online page</title>
</head>
<body>
    <div style="text-align:center; margin-top:40px; font-family:monospace;">
     <img src="./online.svg" width="80" />
     <p>您已經連接網絡...</p>
   </div>
    <script>
    // 註冊 service worker
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./service-worker.js').then(function(registration) {
        // 註冊成功
        console.log('ServiceWorker registration successful with scope: ', registration.scope);
    }).catch(function(err) {
        // 註冊失敗
        console.log('ServiceWorker registration failed: ', err);
       });
    }
    </script>
</body>
</html>

service-worker.js

'use strict';

var cacheVersion = 0;
var currentCache = {
  offline: 'offline-cache' + cacheVersion
};
const offlineUrl = 'offline.html';

this.addEventListener('install', event => {
  event.waitUntil(
    caches.open(currentCache.offline).then(function(cache) {
      return cache.addAll([
          './offline.svg',
          offlineUrl
      ]);
    })
  );
});

this.addEventListener('fetch', event => {

  if (event.request.mode === 'navigate' || (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
        event.respondWith(
          fetch(event.request.url).catch(error => {
              // Return the offline page
              return caches.match(offlineUrl);
        })
     );
  }
  else{
        event.respondWith(caches.match(event.request)
                        .then(function (response) {
                        return response || fetch(event.request);
                    })
            );
      }
});
發佈了82 篇原創文章 · 獲贊 82 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章