Service Worker 離線無法緩存Post請求的問題解決


許多非REST API甚至可以用於讀取數據的POST請求:典型的例子是graphql、soap和其他rpcpapi。但是,Post請求不能在一個現成的漸進式Web應用程序中緩存和脫機使用。瀏覽器的緩存API不會接受它們。下面是一個在IncedB中使用自定義緩存的解決方案。

幸運的是Service Worker可以截獲任何異步請求,因此我們處理POST請求沒有問題。缺少的是在離線時緩存它們並檢索相應的響應的可能性。

 

在下面的示例中,我們將在indexeddb中創建一個簡單的緩存,並將其用作回退(networkfirst策略)。注意,我們將使用整個請求的字符串化版本作為緩存鍵。這是非常安全的,但會為具有長主體和/或頭的請求生成大密鑰。如果這對您來說是一個問題,您可以選擇另一種方法來構建鍵,或者只使用lz字符串或類似的庫來壓縮它。

 

一句警告

當然,有一些原因可以解釋為什么post請求不屬於當前Service Worker規范的一部分。以下是其中一些:

 

從理論上講,POSTs將修改數據。當然,您不能使用正在嘗試修改的數據緩存版本。所以不要緩存任何類型的修改請求!

這些API使用文章進行閱讀,通常不會為每個可能的結果提供單獨的URL。相反,它們只有一個URL來檢索不同的數據集,並接受請求主體中某種類型的查詢。這意味着,您不能緩存基於URL的響應(就像緩存API一樣),而是需要使用自己的組合鍵,包括主體和可能的一些頭。

所以在將post請求緩存到您的數據之前,請仔細考慮!如果您正在尋找一種使修改post請求離線工作的方法,我建議您查看用於延遲發布的Mozilla代碼配方。

 

如何在服務工作進程中緩存發布請求

對於緩存所有post請求的工作示例,請將此代碼放在serviceworker.js中。我在這里使用dexie.js訪問indexeddb,但是任何其他方法也可以。

https://dexie.org/

 

importScripts('your/path/to/dexie/dist/dexie.min.js');

// Listen to fetch requests
self.addEventListener('fetch', function(event) {
// We will cache all POST requests, but in the real world, you will probably filter for
// specific URLs like if(... || event.request.url.href.match(...))
if(event.request.method === "POST"){

// Init the cache. We use Dexie here to simplify the code. You can use any other
// way to access IndexedDB of course.
var db = new Dexie("post_cache");
db.version(1).stores({
post_cache: 'key,response,timestamp'
})

event.respondWith(
// First try to fetch the request from the server
fetch(event.request.clone())
.then(function(response) {
// If it works, put the response into IndexedDB
cachePut(event.request.clone(), response.clone(), db.post_cache);
return response;
})
.catch(function() {
// If it does not work, return the cached response. If the cache does not
// contain a response for our request, it will give us a 503-response
return cacheMatch(event.request.clone(), db.post_cache);
})
);
}
})

/**
* Serializes a Request into a plain JS object.
* 
* @param request
* @returns Promise
*/ 
function serializeRequest(request) {
var serialized = {
url: request.url,
headers: serializeHeaders(request.headers),
method: request.method,
mode: request.mode,
credentials: request.credentials,
cache: request.cache,
redirect: request.redirect,
referrer: request.referrer
};

// Only if method is not `GET` or `HEAD` is the request allowed to have body.
if (request.method !== 'GET' && request.method !== 'HEAD') {
return request.clone().text().then(function(body) {
serialized.body = body;
return Promise.resolve(serialized);
});
}
return Promise.resolve(serialized);
}

/**
* Serializes a Response into a plain JS object
* 
* @param response
* @returns Promise
*/ 
function serializeResponse(response) {
var serialized = {
headers: serializeHeaders(response.headers),
status: response.status,
statusText: response.statusText
};

return response.clone().text().then(function(body) {
serialized.body = body;
return Promise.resolve(serialized);
});
}

/**
* Serializes headers into a plain JS object
* 
* @param headers
* @returns object
*/ 
function serializeHeaders(headers) {
var serialized = {};
// `for(... of ...)` is ES6 notation but current browsers supporting SW, support this
// notation as well and this is the only way of retrieving all the headers.
for (var entry of headers.entries()) {
serialized[entry[0]] = entry[1];
}
return serialized;
}

/**
* Creates a Response from it's serialized version
* 
* @param data
* @returns Promise
*/ 
function deserializeResponse(data) {
return Promise.resolve(new Response(data.body, data));
}

/**
* Saves the response for the given request eventually overriding the previous version
* 
* @param data
* @returns Promise
*/
function cachePut(request, response, store) {
var key, data;
getPostId(request.clone())
.then(function(id){
key = id;
return serializeResponse(response.clone());
}).then(function(serializedResponse) {
data = serializedResponse;
var entry = {
key: key,
response: data,
timestamp: Date.now()
};
store
.add(entry)
.catch(function(error){
store.update(entry.key, entry);
});
});
}

/**
* Returns the cached response for the given request or an empty 503-response for a cache miss.
* 
* @param request
* @return Promise
*/
function cacheMatch(request) {
return getPostId(request.clone())
.then(function(id) {
return store.get(id);
}).then(function(data){
if (data) {
return deserializeResponse(data.response);
} else {
return new Response('', {status: 503, statusText: 'Service Unavailable'});
}
});
}

/**
* Returns a string identifier for our POST request.
* 
* @param request
* @return string
*/

function getPostId(request) {

                        return new Promise((resolve, reject) => {

                                    return JSON.stringify(serializeRequest(request.clone()));

                        });

 }

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM