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,但是任何其他方法也可以。
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()));
});
}
Service Worker 离线无法缓存Post请求的问题解决的更多相关文章
- 前端存储 (5) - service worker 离线存储
service worker 离线存储 简介: 一般的网站 在我们无法访问的 时候 一般 回出现 如下 该网页无法访问 service worker 构建的网站不会出现这个错误,因为所有的 请求都是先 ...
- Service Worker和HTTP缓存
很多人,包括我自己,初看Service Worker多一个Cache Storage的时候,就感觉跟HTTP长缓存没什么区别. 例如大家讲的最多的Service Worker能让网页离线使用,但熟悉H ...
- 浏览器缓存和Service Worker
浏览器缓存和Service Worker @billshooting 2018-05-06 字数 6175 Follow me on Github 标签: BOM 1. 传统的HTTP浏览器缓存策略 ...
- [转] service worker初探:超级拦截器与预缓存
在2014年,W3C公布了service worker的草案,service worker提供了很多新的能力,使得web app拥有与native app相同的离线体验.消息推送体验. service ...
- Service Worker 缓存文件处理
交代背景 前段时间升级了一波Google Chrome,发现我的JulyNovel站点Ctrl+F5也刷新不了,后来发现是新的Chrome已经支持Service Worker,而我的JulyNovel ...
- 渐进式web应用开发---service worker (二)
阅读目录 1. 创建第一个service worker 及环境搭建 2. 使用service worker 对请求拦截 3. 从web获取内容 4. 捕获离线请求 5. 创建html响应 6. 理解 ...
- 渐进式web应用开发---Service Worker 与页面通信(七)
_ 阅读目录 一:页面窗口向 service worker 通信 二:service worker 向所有打开的窗口页面通信 三:service worker 向特定的窗口通信 四:学习 Messag ...
- 浅析Service Worker
一.service worker是什么? 平常浏览器窗口中跑的页面运行的是主JavaScript线程,DOM和window全局变量都是可以访问的. Service Worker是走的另外的线程,可以理 ...
- service worker介绍
原文:Service workers explained 译者:neal1991 welcome to star my articles-translator, providing you advan ...
随机推荐
- win10下安装mongodb
环境 :win10 mongodb-win32-x86_64-v3.4-latest.zip 1.下载地址:https://www.mongodb.com/download-center/commu ...
- rm: cannot remove ' xxx': Permission denied
一般出现这个问题的原因是权限被改变了,使用chown 修改即可 可参考:chmod.chgrp.chown详解 linux chmod命令和chown命令
- latex如何定义宏,插图统一尺寸减少工作量
问题背景是这样的,因为我要在文中插入一系列的图片,但是这些图片的大小我要保持一致,来达到预期的效果. 比如我有三个figure,这三个figure中,每个figure里面有两行,5列图片,我想要的是, ...
- sqlalchemy执行sql语句
#删除user表中的所有数据,表中的自增字段又从默认值开始 session.execute("truncate table user")
- dee
窗口居中def center(self): screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((s ...
- HashMap源码解析(简单易懂)
/* 每一个key-value存储在Node<K,V>中,HashMap由Node<K,V>[]数 组组成. */ static class Node<K,V> i ...
- Linux搜索文件或内容
1.递归搜索文件内容,如果查找"hello,world!"字符串,可以这样: grep -rn "hello,world!" * * : 表示当前目录所有文件, ...
- Hibernate5.3 + mysql8.0遇到的问题
今天学习Hibernate看的是旧版本的视频Hibernate4.0版本 遇到几个新旧版本的区别. 1.方言,这个是因为SQL不是因为Hibernate 新版方言 2.将编译的类配置进congifur ...
- hdu 1372 BFS
A friend of you is doing research on the Traveling Knight Problem (TKP) where you are to find the sh ...
- 渗透测试学习 三、Linux基础
Linux发行版本 内核+应用程序 打包在一起 一.优点: 完全免费 完全兼容POSIX 1.0标准 多用户,多任务 良好的界面 可靠安全稳定的性能 支持多种平台 丰富的网络功能 安全性更好(针对 ...