Могут ли работники сервиса кешировать 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-запросы? Если они не могут, какой подход мы можем использовать, чтобы сделать приложения действительно автономными?

6 ответов

Решение

Вы не можете кэшировать POST-запросы, используя Cache API. См. https://slightlyoff.github.io/ServiceWorker/spec/service_worker/ (пункт 2.2).

В репозитории спецификаций есть соответствующее обсуждение: https://github.com/slightlyoff/ServiceWorker/issues/693

Интересное решение представлено в Поваренной книге ServiceWorker: https://serviceworke.rs/request-deferrer.html По сути, решение сериализует запросы к IndexedDB.

Я использовал следующее решение в недавнем проекте с API GraphQL: я кэшировал все ответы от маршрутов 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-обертки. Не стесняйтесь использовать его!

Если вы говорите о данных формы, то вы можете перехватить событие извлечения и прочитать данные формы аналогично приведенному ниже, а затем сохранить данные в 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/ (пункт 4).

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

Хотя согласно принятому ответу «Вы не можете кэшировать POST-запросы с помощью Cache API»… кажется, что на самом деле вы можете.

Возможно, есть веские причины избегать этого в обычном порядке из-за характера запросов... но если вам нужно, то это вполне возможно. В моем случае я бы предпочел использовать (с соответствующей информацией для операции в URL-адресе), но вместо этого мне приходится публиковать соответствующую информацию через тело a, чтобы избежать (иногда) ограничения длины URL-адреса. Но по сути я просто использую здесь обходной путь, и, на мой взгляд, я все еще использую его как , поэтому кажется разумным, что в этой ситуации я должен иметь возможность кэшировать ответы на мои запросы... просто используя ключ кэша основан на том, что находится в теле, а не на том, что находится в URL-адресе.

Все, что вам нужно сделать, это клонировать запрос и преобразовать его в запрос... использовать клонированный запрос в качестве основы для кэширования в кеше сервисного работника, но использовать исходный запрос для получения ответа, который должен быть кэширован.

Что-то вроде следующего:

        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запрос, потому что это просто строка, а не запрос.

Другие вопросы по тегам