【问题标题】:Append string to user agent within Service Worker将字符串附加到 Service Worker 中的用户代理
【发布时间】:2018-11-14 12:06:08
【问题描述】:

我已经被这个问题困扰了一段时间了。我进行了深入研究,并花了很多时间研究关于 SO 的类似问题 - 但没有成功。

一点背景。我有一个网站和一个 android 应用程序,它有效地以不同的形式呈现网站,具有不同的显示等......该应用程序知道从传入的请求中执行此操作 - 因为用户在用户代理上附加了一个特定的字符串(我们只是说字符串是“MobileAppRequest”)。在用户代理中检测到此字符串后,服务器知道返回不同的 html 文件。这使用户仍然可以通过浏览器访问网站并使用网络版本,并获得来自他们的 Android 应用的应用体验。

现在,在使用服务工作者时,它默认为用户的标准用户代理,而不包括应用程序中附加的字符串。然后,这将返回应用程序中的网络版本,这一切都搞砸了。我需要知道如何设置自定义标头,或者实际上,将字符串附加到服务工作者中的用户代理。它说当我尝试直接更改标头时标头是不可变的,但我知道解决此问题的方法是发出一个新请求作为响应。

这是我的 SW.js

var CACHE_STATIC_NAME = 'static-v10';
var CACHE_DYNAMIC_NAME = 'dynamic-v2';

self.addEventListener('install', function (event) {
  console.log('[Service Worker] Installing Service Worker ...', event);
  event.waitUntil(
    caches.open(CACHE_STATIC_NAME)
      .then(function (cache) {
        console.log('[Service Worker] Precaching App Shell');
        cache.addAll([
          '/',
          '/static/media/next.png',
          '/static/media/previous.png'
        ]);
      })
  )
});

self.addEventListener('activate', function (event) {
  console.log('[Service Worker] Activating Service Worker ....', event);
  event.waitUntil(
    caches.keys()
      .then(function (keyList) {
        return Promise.all(keyList.map(function (key) {
          if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
            console.log('[Service Worker] Removing old cache.', key);
            return caches.delete(key);
          }
        }));
      })
  );
  return self.clients.claim();
});

self.addEventListener('fetch', function(event) {
  event.respondWith(
    fetch(event.request)
      .then(function(res) {
        return caches.open(CACHE_DYNAMIC_NAME)
                .then(function(cache) {
                  cache.put(event.request.url, res.clone());
                  return res;
                })
      })
      .catch(function(err) {
        return caches.match(event.request);
      })
  );
});

我希望答案包含完整的 sw.js 或您正在编辑的整个函数,这样我就不会在集成回代码时犯任何错误。

【问题讨论】:

  • 如果用户的浏览器注册了Service Worker,为什么在创建Service Worker的时候不通过浏览器的用户代理呢?这样你就不必处理 SW 用户代理了。
  • “通过”浏览器的用户代理是什么意思?如何做到这一点?
  • 与postMessage(),是一种方式。

标签: javascript browser-cache service-worker


【解决方案1】:

正如我在 cmets 中提到的,如果你使用 postMessage(),你可以将任何你想要的数据传递给 Service Worker。

以下是使用您的脚本的示例。

webpage.html

<script>
  // creates ServiceWorker
  var worker = new Worker('SW.js');
  // sends the browser's userAgent to the ServiceWorker
  worker.postMessage(window.navigator.userAgent);
</script>

SW.js

var CACHE_STATIC_NAME = 'static-v10';
var CACHE_DYNAMIC_NAME = 'dynamic-v2';
// defines a global variable that will hold the UserAgent
var UA = '';

// listens to incoming postMessage()
self.addEventListener("message", function(event) {
  // update the UA variable with the event.data, which is the browser's UserAgent
  UA = event.data;
  console.log('ServiceWorker now has UA variable defined as ', UA);
}, false);

self.addEventListener('install', function (event) {
  console.log('[Service Worker] Installing Service Worker ...', event);
  event.waitUntil(
    caches.open(CACHE_STATIC_NAME)
      .then(function (cache) {
        console.log('[Service Worker] Precaching App Shell');
        cache.addAll([
          '/',
          '/static/media/next.png',
          '/static/media/previous.png'
        ]);
      })
  )
});

self.addEventListener('activate', function (event) {
  console.log('[Service Worker] Activating Service Worker ....', event);
  event.waitUntil(
    caches.keys()
      .then(function (keyList) {
        return Promise.all(keyList.map(function (key) {
          if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
            console.log('[Service Worker] Removing old cache.', key);
            return caches.delete(key);
          }
        }));
      })
  );
  return self.clients.claim();
});

self.addEventListener('fetch', function(event) {
  // var myHeader = new Headers(event.request.headers);
  // myHeader.append('CUSTOMHEADER', 'CUSTOMVALUE');
  // you can now make a request using the passed UA
  event.respondWith(
    fetch(event.request, {credentials: 'same-origin', mode: 'cors', redirect: 'follow', userAgent: UA}).then(function(res) {
        return caches.open(CACHE_DYNAMIC_NAME)
                .then(function(cache) {
                  cache.put(event.request.url, res.clone());
                  return res;
                })
      })
      .catch(function(err) {
        return caches.match(event.request);
      })
  );
});

虽然浏览 SO 是很好的示例,但有时当您的选择有限时,最好参考文档。

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

【讨论】:

    【解决方案2】:

    我已编辑您的代码以在获取请求中添加自定义标头。你应该尝试一下,它会工作的。

    对于您的用例,您只想为您的域请求添加自定义标头,因此我添加了一个正则表达式检查,它将检查并添加自定义标头以仅获取与您的域相关的请求。 (为本地测试添加了 localhost)。对于与其他域有关的获取请求,它将发送标准标头。

    你需要传递一些额外的参数来获取我已经添加的调用。 在您的应用程序中,根据需要编辑“CUSTOMHEADER”、“CUSTOMVALUE”和 YOURDOMAIN.com。

    在https://developer.mozilla.org/en-US/docs/Web/API/Headers 上阅读有关 Header() 的更多信息。 如果您想修改用户代理,请在答案中的 fetch 事件中添加以下行,

      var myHeader = new Headers(event.request.headers);
      myHeader.delete('User-Agent');
      myHeader.set('User-Agent', 'CUSTOM_USERAGENTVALUE');
    

    请注意:目前不建议更改“User-Agent”,因为最近它已从禁止标头列表中删除。所以浏览器支持是有限的。

    var CACHE_STATIC_NAME = 'static-v10';
    var CACHE_DYNAMIC_NAME = 'dynamic-v2';
    
    self.addEventListener('install', function (event) {
      console.log('[Service Worker] Installing Service Worker ...', event);
      event.waitUntil(
        caches.open(CACHE_STATIC_NAME)
          .then(function (cache) {
            console.log('[Service Worker] Precaching App Shell');
            cache.addAll([
              '/',
              '/static/media/next.png',
              '/static/media/previous.png'
            ]);
          })
      )
    });
    
    self.addEventListener('activate', function (event) {
      console.log('[Service Worker] Activating Service Worker ....', event);
      event.waitUntil(
        caches.keys()
          .then(function (keyList) {
            return Promise.all(keyList.map(function (key) {
              if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
                console.log('[Service Worker] Removing old cache.', key);
                return caches.delete(key);
              }
            }));
          })
      );
      return self.clients.claim();
    });
    
    self.addEventListener('fetch', function(event) {
        if(/^.*\:\/\/(?:localhost|YOURDOMAIN\.com)\/.*/.test(event.request.url))
        { var myHeader = new Headers(event.request.headers);
          myHeader.append('CUSTOMHEADER', 'CUSTOMVALUE');
          console.log('Custom Fetch Call:'+event.request.url);
          event.respondWith(
            fetch(event.request.url, {credentials: 'same-origin', redirect: 'follow', headers: myHeader}).then(function(res) {
                return caches.open(CACHE_DYNAMIC_NAME)
                        .then(function(cache) {
                          cache.put(event.request.url, res.clone());
                          return res;
                        })
              })
              .catch(function(err) {
                return caches.match(event.request);
              })
          );
          return
        }
        event.respondWith(
          fetch(event.request).then(function(res) {
            return caches.open(CACHE_DYNAMIC_NAME)
                    .then(function(cache) {
                      cache.put(event.request.url, res.clone());
                      return res;
                    })
          })
          .catch(function(err) {
            return caches.match(event.request);
          })
        );  
    
    
    });
    

    您可以在服务echo $_SERVER['HTTP_CUSTOMHEADER']; 上查看它,它会回显CUSTOMVALUE。我已经通过服务器端和 chrome 网络面板交叉验证了它的工作。

    为了向您的 android 用户提供自定义应用页面,您应该使用 (IN PHP):

    $INSIDEAPP = (isset($_SERVER['HTTP_USER_AGENT']) && (strpos($_SERVER['HTTP_USER_AGENT'],'CUSTOMUSERAGENT') !== false))? true:false;
    $INSIDE_SW = (isset($_SERVER['HTTP_CUSTOMHEADER']))?true:false;
    
    $SERVE_APP_PAGE = $INSIDEAPP || $INSIDE_SW;
    
    if($SERVE_APP_PAGE)
    {
    //serve request of app page.
    }
    else
    {
    // serve normal request through browser
    }
    

    【讨论】:

    • 它对你有用吗?对我不起作用。 Cors 错误,即使我将模式更改为 no-cors。它说:“”的 FetchEvent 导致网络错误响应:“不透明”响应用于类型不是 no-cors 的请求。另外,没有添加自定义标题(我在注册 sw 后在主页加载时使用用户代理的控制台日志检查)。
    • 我已经编辑了代码并删除了模式:'cors'。我还检查了代码,它工作正常并按照定义添加了自定义标题。注意:您需要在初始加载后刷新页面一次才能看到它的工作。在初始页面加载服务工作者激活和后续页面加载服务工作者拦截请求并添加标题。
    • 我用这段代码更新了它,但它仍然显示相同的标准 UA。我在注册 sw 后刷新,但控制台日志仍然显示相同的 UA。你在chrome上测试过吗?在调整 sw 后,您如何验证您的标头是否已更改?
    • 我不建议您更改服务工作者请求的用户代理,因为它可能无法在某些旧浏览器中工作,因为它在受限列表中并且最近已被删除。您可以在 fetch 调用和服务器端添加自定义标头,将应用页面提供给包含您的自定义用户代理(在 android webview 中设置)的请求以及具有自定义标头的请求。
    • 我再次编辑了代码并添加了获取匹配模式和其他调整。我已经交叉验证它在服务器端以及 chrome 面板上工作。因为,在 chrome 面板上,有时它不会显示请求标头。您现在应该尝试使用已编辑的代码。
    猜你喜欢
    • 2013-02-09
    • 2013-01-19
    • 1970-01-01
    • 2014-03-27
    • 2019-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多