服务工作线程是否可以缓存 POST 请求



我试图在获取事件时在服务工作者中缓存 POST 请求。

我用了cache.put(event.request, response),但返回的承诺被拒绝了TypeError: Invalid request method POST.

当我尝试点击相同的 POST API 时,caches.match(event.request)给了我未定义。

但是当我对 GET 方法做同样的事情时,它奏效了:caches.match(event.request) GET 请求给了我一个响应。

服务工作线程可以缓存 POST 请求吗?如果他们不能,我们可以使用什么方法来使应用程序真正离线?

您无法使用 Cache API 缓存 POST 请求。见 https://w3c.github.io/ServiceWorker/#cache-put(第4点(。

规范存储库中有一个相关的讨论:https://github.com/slightlyoff/ServiceWorker/issues/693

一个有趣的解决方案是 ServiceWorker Cookbook 中介绍的解决方案:https://serviceworke.rs/request-deferrer.html基本上,该解决方案将请求序列化到 IndexedDB。

我在最近的一个带有 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包装器。随意使用它!

如果您谈论的是表单数据,那么您可以拦截 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... )
      }
})

另一种提供完整离线体验的方法可以通过使用 Cloud Firestore 离线持久性来获取。

POST/PUT请求在本地缓存的数据库上执行,然后在用户恢复其互联网连接后立即自动同步到服务器(请注意,尽管有500个离线请求的限制(。

遵循此解决方案要考虑的另一个方面是,如果多个用户具有同时同步的脱机更改,则无法保证更改将在服务器上按正确的时间顺序执行,因为 Firestore 使用先到先得的逻辑。

根据 https://w3c.github.io/ServiceWorker/#cache-put(第4点(。

        if(request.method !== "GET") {
            return Promise.reject('no-match')
        }

尽管根据接受的答案"您无法使用缓存 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请求,因为它只是一个字符串,而不是请求。

最新更新