Only in Chrome (Service Worker): '… a redirect

2019-02-11 01:56发布

问题:

When I refresh (or go offline) in Chrome then I get "This site can't be reached" and the following logged to console: The FetchEvent for "http://localhost:8111/survey/174/deployment/193/answer/offline/attendee/240/" resulted in a network error response: a redirected response was used for a request whose redirect mode is not "follow".. When I refresh in Firefox everything works fine. Could someone explain why this is happening?

Here is my simplified SW.

importScripts("/static/js/libs/idb.js")

var CACHE_NAME = "upshot-cache-version3"
var urlsToCache = [...]

self.addEventListener("install", event => {
  event.waitUntil(
    caches
      .open(CACHE_NAME)
      .then(cache => {
        urlsToCache.map(url => cache.add(url))
      })
  )
})

self.addEventListener("activate", event => {
  clients.claim()
})

self.addEventListener('fetch', event => {
  event.respondWith(
    caches
      .match(event.request)
      .then(response => {

        if (response) {
          return response
        }

        var fetchRequest = event.request.clone()

        return fetch(fetchRequest).then(response => {
          if (!response || response.status !== 200 || response.type !== 'basic') {
            return response
          }
          var responseToCache = response.clone()
          caches.open(CACHE_NAME).then(cache => cache.put(event.request, responseToCache))
          return response
        })

      })
  )
})

回答1:

This is due to a (relatively) recent change in the security restrictions around what kind of responses can be used to satisfy navigations. It should apply to all browsers that support service workers (i.e. both Chrome and Firefox today), but it's possible that you're testing with a version of Firefox that's out of date.

The background on what changed can be found in this issue tracker entry, and there's also more background on the decision that led to the underlying specification.

In terms of modifying your service worker to handle the security restriction, if you're currently responding to navigation requests for certain URLs with a HTTP 30x redirect to a different URL, you'll need to take care not to just store that redirected response directly in the cache.

You can tell whether a given response was redirected by checking whether response.redirected is true, and if so, use code along the lines of this (adapted from the Workbox project) to create a "clean" copy of the response that could then be stored in the cache:

function cleanResponse(response) {
  const clonedResponse = response.clone();

  // Not all browsers support the Response.body stream, so fall back to reading
  // the entire body into memory as a blob.
  const bodyPromise = 'body' in clonedResponse ?
    Promise.resolve(clonedResponse.body) :
    clonedResponse.blob();

  return bodyPromise.then((body) => {
    // new Response() is happy when passed either a stream or a Blob.
    return new Response(body, {
      headers: clonedResponse.headers,
      status: clonedResponse.status,
      statusText: clonedResponse.statusText,
    });
  });
}