服务工作者能否缓存POST请求?

38

我尝试在fetch事件中的service worker中缓存POST请求。

我使用了cache.put(event.request, response),但返回的Promise被拒绝,并显示TypeError: Invalid request method POST.

当我尝试访问同一POST API时,caches.match(event.request)返回undefined。

但是对于GET方法,同样的操作是有效的:caches.match(event.request)对于GET请求会返回响应。

服务工作者可以缓存POST请求吗? 如果不能,我们可以采用什么方法使应用程序真正脱机?


尝试起来应该非常容易 :) - Marco Castelluccio
6个回答

41

我没有看到任何关于PATCH或DELETE请求的提及。它们是一样的吗? - Scipion
补丁和删除应该具有相同的功能。您将在服务工作者中收到“获取”事件,可以检查方法 - GET、POST、PATCH、DELETE - 并执行所需操作。 - Judah Gabriel Himango
服务工作者菜谱示例可以在此处找到:https://github.com/mozilla/serviceworker-cookbook/tree/master/request-deferrer - run_the_race

13

我在最近的一个GraphQL API项目中使用了以下解决方案:我将API路由的所有响应都缓存到IndexedDB对象存储中,并使用序列化表示的请求作为缓存键。然后,如果网络不可用,我将缓存用作备用方案:

// ServiceWorker.js
self.addEventListener('fetch', function(event) {
    // We will cache all POST requests to matching URLs
    if(event.request.method === "POST" || event.request.url.href.match(/*...*/)){
        event.respondWith(
            // First try to fetch the request from the server
        fetch(event.request.clone())
            // If it works, put the response into IndexedDB
            .then(function(response) {
                // Compute a unique key for the POST request
                var key = getPostId(request);
                // Create a cache entry
                var entry = {
                    key: key,
                    response: serializeResponse(response),
                    timestamp: Date.now()
                };

                /* ... save entry to IndexedDB ... */

                // Return the (fresh) response
                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
                var key = getPostId(request);
                var cachedResponse = /* query IndexedDB using the key */;
                return response;
            })
        );
    }
})

function getPostId(request) {
    /* ... compute a unique key for the request incl. it's body: e.g. serialize it to a string */
}

这里是我使用Dexie.js作为IndexedDB包装器的具体解决方案的完整代码。随意使用!


3
尽管这个链接可能回答了问题,但更好的做法是在此处包含答案的主要部分,并提供链接作为参考。仅有链接的答案如果链接的页面发生变化就会失效。-【来自审查】 - Liam
1
抱歉,我已经修改了答案并包含了代码摘要(整个代码相当长)。或者我应该无论代码长度如何都发布整个代码? - A. Kabachnik
一个代码片段应该就行了。如果你只是发布自己博客的链接,看起来很像垃圾邮件。现在对我来说好多了。 - Liam
如果你想要推广自己的博客,我建议你阅读一下以下内容:回答中自我推广的限制 - Liam

2

2
另一种提供完全离线体验的方法是使用Cloud Firestore离线持久性
在本地缓存数据库上执行POST / PUT请求,然后在用户恢复互联网连接时自动将其同步到服务器(请注意,离线请求的数量限制为500)。
遵循此解决方案需要考虑的另一个方面是,如果多个用户具有同时被同步的离线更改,则不能保证更改将按正确的时间顺序在服务器上执行,因为Firestore使用先到先服务逻辑。

2
如果您在谈论表单数据,那么您可以拦截fetch事件,并以类似以下方式读取表单数据,然后将数据保存在indexedDB中。
//service-worker.js
self.addEventListener('fetch', function(event) {
      if(event.request.method === "POST"){
         var newObj = {};

               event.request.formData().then(formData => {

                for(var pair of formData.entries()) {
                  var key = pair[0];
                  var value =  pair[1];
                  newObj[key] = value;
                }

              }).then( ...save object in indexedDB... )
      }
})

1

尽管根据被接受的答案“您无法使用缓存API缓存POST请求”...但实际上似乎是可以的。

也许有很好的理由避免这样做,因为POST请求的性质...但如果你必须这样做,那么似乎完全可能。在我的情况下,我宁愿使用GET(在URL中包含相关信息的GET操作)而不是通过POST的主体发布相关信息,以避免(有时)遇到URL长度限制。但实际上我只是在这里使用POST作为解决方法,在我看来我仍然真正地将其用作GET,因此在这种情况下,我应该能够缓存响应我的POST请求...只需使用基于POST主体中内容的缓存键而不是GET URL中的内容。

你所要做的就是克隆 POST 请求并将其转换为 GET 请求... 使用克隆的 GET 请求作为服务工作者缓存的基础,但使用原始的 POST 请求获取要被缓存的响应。

大致如下:

  if (request.method.toUpperCase() === "POST") {
    // get the body text...
    const body = await request.clone().text();
    // create a new URL for the purposes of a cache key...
    const cacheUrl = new URL(request.url);
    // create an augmented URL by appending the body to the original pathname...
    cacheUrl.pathname = cacheUrl.pathname + body;
    // convert the request to a GET to be able to cache it...
    const cacheRequest = new Request(cacheUrl.toString(), {
      headers: request.headers,
      method: "GET"
    });
    // get cache...
    const cache = caches.default;
    // check if there is a cached response in the cache based on the cloned GET request (for the cache key) NOT the original POST request...
    let response = await cache.match(cacheRequest);
    // if not, fetch the response using the original POST request...
    if (!response) {
      response = await fetch(request);
      // put the response into the cache using the cloned GET request (for the cache key) NOT the original POST request...
      event.waitUntil(cache.put(cacheRequest, response.clone()));
    }
    return response;
  }

在我的情况下,我实际上并没有将克隆的请求传递到缓存API中,而是只传递了一个字符串缓存键,因此根本不需要创建虚拟的GET请求... 我实际上只是直接将派生的字符串cacheUrl.pathname传递给cache.match()cache.put()... 它们不会将其拒绝为POST请求,因为它只是一个字符串,而不是一个请求。

我做了差不多一样的事情,克隆了POST请求,将方法改为GET,但是缓存了克隆的请求。这是一个技巧,但对我们来说效果很好。 - jsaddwater

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接