代码之家  ›  专栏  ›  技术社区  ›  ashawley

服务工作人员可以获取和缓存跨源资产吗?

  •  1
  • ashawley  · 技术社区  · 6 年前

    我正在使用来自 Progressive Web app tutorial by Google 但我得到了一个错误:

    Uncaught (in promise) TypeError:
     Failed to execute 'clone' on 'Response':
     Response body is already used
    

    该网站使用第三方的javascript和样式表作为Web字体。 我想将托管在这些cdn上的资产添加到脱机缓存中。

    addEventListener("fetch", function(e) {
      e.respondWith(
        caches.match(e.request).then(function(response) {
            return response || fetch(e.request).then(function(response) {
            var hosts = [
              "https://fonts.googleapis.com",
              "https://maxcdn.bootstrapcdn.com",
              "https://cdnjs.cloudflare.com"
            ];
            hosts.map(function(host) {
              if (e.request.url.indexOf(host) === 0) {
                caches.open(CACHE_NAME).then(function(cache) {
                  cache.put(e.request, response.clone());
                });
              }
            });
            return response;
          });
        })
      );
    });
    

    这些是在流行的cdn上托管的,所以我的预感是他们应该为cors头做正确的事情。

    以下是HTML中我要缓存的资产:

    <link rel="stylesheet" type="text/css"
          href="https://fonts.googleapis.com/css?family=Merriweather:900,900italic,300,300italic">
    <link rel="stylesheet" type="text/css"
          href="https://fonts.googleapis.com/css?family=Lato:900,300" rel="stylesheet">
    <link rel="stylesheet" type="text/css"
          href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css">
    <script type="text/javascript" async
            src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
    </script>
    

    根据控制台日志,服务工作者是 尝试 要获取这些资产:

    Fetch finished loading:
     GET "https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css".
     sw.js:32
    Fetch finished loading:
     GET "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML".
     sw.js:32
    Fetch finished loading:
     GET "https://fonts.googleapis.com/css?family=Merriweather:900,900italic,300,300italic".
     sw.js:32
    Fetch finished loading:
     GET "https://fonts.googleapis.com/css?family=Lato:900,300".
     sw.js:32
    Fetch finished loading:
     GET "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/config/TeX-AMS-MML_HTMLorMML.js?V=2.7.1".
     sw.js:32
    Fetch finished loading:
     GET "https://maxcdn.bootstrapcdn.com/font-awesome/latest/fonts/fontawesome-webfont.woff2?v=4.7.0".
     sw.js:32
    

    如果我删除 clone ,如中建议的 Why does fetch request have to be cloned in service worker? ,我会得到同样的错误:

    TypeError: Response body is already used
    

    如果我添加 { mode: "no-cors" } 到取货处 Service worker CORS issue ,我将得到相同的错误和这些警告:

    The FetchEvent for
     "https://maxcdn.bootstrapcdn.com/font-awesome/latest/fonts/fontawesome-webfont.woff2?v=4.7.0"
     resulted in a network error response: an "opaque" response was
     used for a request whose type is not no-cors
    The FetchEvent for
     "https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh50XSwiPGQ3q5d0.woff2"
     resulted in a network error response: an "opaque" response was
     used for a request whose type is not no-cors
    The FetchEvent for
     "https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwiPGQ3q5d0.woff2"
     resulted in a network error response: an "opaque" response was
     used for a request whose type is not no-cors
    The FetchEvent for
     "https://fonts.gstatic.com/s/merriweather/v19/u-4n0qyriQwlOrhSvowK_l521wRZWMf6hPvhPQ.woff2"
     resulted in a network error response: an "opaque" response was
     used for a request whose type is not no-cors
    

    我可以将这些资产添加到服务工作者的静态缓存中 install 事件,但我有理由仅在 fetch 事件。

    2 回复  |  直到 6 年前
        1
  •  2
  •   Jeff Posnick    6 年前

    你的使用是正确的 clone() 但时机很重要。你得打电话给我 克隆() 决赛前 return response 执行,因为此时,响应将传递到服务工作者的客户机页面,其主体将被“消耗”。

    有两种方法可以解决这个问题:要么打电话 克隆() 在执行异步缓存代码之前,或者延迟 返回响应 语句,直到缓存完成。

    我建议采用第一种方法,因为这意味着你会尽快得到对页面的响应。我还建议您重写代码以使用 async / await ,因为它的可读性更高(而且现在还支持服务人员的任何浏览器都支持它)。

    addEventListener("fetch", function(e) {
      e.respondWith((async function() {
        const cachedResponse = await caches.match(e.request);
        if (cachedResponse) {
          return cachedResponse;
        }
    
        const networkResponse = await fetch(e.request);
    
        const hosts = [
          'https://fonts.googleapis.com',
          'https://maxcdn.bootstrapcdn.com',
          'https://cdnjs.cloudflare.com',
        ];
    
        if (hosts.some((host) => e.request.url.startsWith(host))) {
          // This clone() happens before `return networkResponse` 
          const clonedResponse = networkResponse.clone();
    
          e.waitUntil((async function() {
            const cache = await caches.open(CACHE_NAME);
            // This will be called after `return networkResponse`
            // so make sure you already have the clone!
            await cache.put(e.request, clonedResponse);
          })());
        }
    
        return networkResponse;
      })());
    });
    

    注: (async function() {})() 语法可能看起来有点奇怪,但这是使用的快捷方式 异步的 / 等待 在将返回承诺的立即执行函数中。见 http://2ality.com/2016/10/async-function-tips.html#immediately-invoked-async-function-expressions

        2
  •  0
  •   ashawley    6 年前

    杰夫·波尼克的回答是对的。

    在执行异步缓存更新之前,需要克隆响应:

            var clonedResponse = response.clone();
            caches.open(CACHE_NAME).then(function(cache) {
              cache.put(e.request, clonedResponse);
            });
    

    这个 Service Worker primer by Google 实际上有正确的代码:

            // IMPORTANT: Clone the response. A response is a stream
            // and because we want the browser to consume the response
            // as well as the cache consuming the response, we need
            // to clone it so we have two streams.
            var responseToCache = response.clone();
    
            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });
    
            return response;
    

    代码中有一个带有“重要”注释的注释,但它是关于克隆的,而不是您遇到的问题。