如何在 HTML5 Web Worker 上控制 XMLHttpRequest object?

How to control the XMLHttpRequest object on an HTML5 Web Worker?

我有一个页面通常会用包装器覆盖 window.XMLHttpRequest,包装器会做一些额外的事情,比如在某些请求中插入 headers。

我在使用 HTML5 Worker 的第 3 方库中有一些功能,我们看到这个请求没有使用 XMLHttpRequest 包装器 object。因此,此库发出的任何请求都缺少所需的 headers,因此请求将失败。

有没有办法控制当前线程创建的任何 Worker 的 XMLHttpRequest?

这个第 3 方库代码如下所示:

        function createWorker(url) {
            var worker = new Worker(url);
            worker.onmessage = function (e) {
                if (e.data.status) {
                    onprogress(e.data.status);
                } else if (e.data.error) {
                    onerror(e.data.error);
                } else {
                    exportUtils.saveFile(new Blob([e.data]), params.fileName);
                    onfinish();
                }
            };
            worker.postMessage(params); // window.location.origin +
            return worker;
        }

上面URL变量返回的Javascript包含这样的代码:

        return new Promise(function(t, r) {
            var n = new XMLHttpRequest
              , a = "batch_" + o()
              , u = e.dataUrl.split(e.serviceUrl)[1]
              , c = [];
            n.onload = function() {
                for (var e = this.responseText, n = this.responseText.split("\r\n"), o = 0, a = n.length, i = a - 1; o < a && "{" !== n[o].slice(0, 1); )
                    o++;
                for (; i > 0 && "}" !== n[i].slice(-1); )
                    i--;
                n = n.slice(o, i + 1),
                e = n.join("\r\n");
                try {
                    var u = JSON.parse(e);
                    t(u)
                } catch (t) {
                    r(s + e)
                }
            }
            ,
            n.onerror = function() {
                r(i)
            }
            ,
            n.onabort = function() {
                r(i)
            }
            ,
            n.open("POST", e.serviceUrl + "$batch", !0),
            n.setRequestHeader("Accept", "multipart/mixed"),
            n.setRequestHeader("Content-Type", "multipart/mixed;boundary=" + a);
            for (var p in e.headers)
                "accept" != p.toLowerCase() && n.setRequestHeader(p, e.headers[p]);
            c.push("--" + a),
            c.push("Content-Type: application/http"),
            c.push("Content-Transfer-Encoding: binary"),
            c.push(""),
            c.push("GET " + u + " HTTP/1.1");
            for (var p in e.headers)
                c.push(p + ":" + e.headers[p]);
            c.push(""),
            c.push(""),
            c.push("--" + a + "--"),
            c.push(""),
            c = c.join("\r\n"),
            n.send(c)
        }
        )

答案既是温和的 "no" 又是最终的 "yes"。

当一段代码 运行 在不同的上下文中(如网络工作者或 iframe),您无法直接控制其全局 object (1).

此外,XMLHttpRequest 并不是发送网络请求的唯一方法 - 您还有其他几种方法,其中主要的是 fetch api.

但是,有一个相对较新的工具叫做 Service Workers,它可以为您提供很多帮助!

服务人员

Service workers(缩写。SWs)非常像你已经知道的网络工作者,但他们不是只在当前页面 运行ning,而是继续 运行只要您的用户留在您的域中,后台。它们对您的整个域也是全局的,因此从您的站点发出的任何请求都将通过它们传递。

他们在生活中的主要目的是响应网络请求,通常用于缓存目的和离线内容、提供推送通知以及其他一些小众用途。

让我们看一个小例子(注意,运行 这些来自本地网络服务器):

// index.html
<script>
navigator.serviceWorker.register('sw.js')
    .then(console.log.bind(console, 'SW registered!'))
    .catch(console.error.bind(console, 'Oh nose!'));

setInterval(() => {
    fetch('/hello/');
}, 5000);
</script>

// sw.js
console.log('Hello from a friendly service worker');

addEventListener('fetch', event => {
    console.log('fetch!', event);
})

这里我们注册了一个 service worker,然后每 5 秒请求一个页面。在 service worker 中,我们简单地记录每个网络事件,这些事件可以在 fetch 事件中捕获。

首次加载时,您应该会看到正在注册的服务工作者。 SW 仅在安装 之后才开始拦截来自第一页的请求...因此刷新页面以开始查看正在记录的 fetch 事件。我建议您在继续阅读之前先了解一下事件属性,这样事情会更清楚。

酷!通过在控制台中查看事件,我们可以看到 event.request 是我们的浏览器构建的 Request object。在理想情况下,我们可以访问 event.request.headers 并添加我们自己的 header!很梦幻吧!?

不幸的是,request/response headers are guarded and immutable。幸运的是,我们是一群固执的人,可以简单地re-construct请求:

// sw.js
console.log('Hello from a friendly service worker');

addEventListener('fetch', event => {
    console.log('fetch!', event);
    // extract our request
    const { request } = event;

    // clone the current headers
    const newHeaders = new Headers();
    for (const [key, val] of request.headers) {
        newHeaders.append(key, val);
    }
    // ...and add one of our own
    newHeaders.append('Say-What', 'You heard me!');

    // clone the request, but override the headers with our own
    const superDuperReq = new Request(request, {
        headers: newHeaders
    });

    // now instead of the original request, our new request will take precedence!
    return fetch(superDuperReq);
});

这是几个不同的概念在起作用,所以如果需要多次获得也没关系。但本质上,我们正在创建一个新的请求来代替原来的请求,并设置一个新的 header!万岁!

坏处

现在,一些缺点:

  • 由于我们劫持了每一个请求,我们可能会不小心更改我们并非故意更改的请求,并有可能破坏整个宇宙!
  • 升级软​​件是一个巨大的痛苦。 SW 生命周期很复杂,很难在用户身上进行调试。我看过一个关于处理它的好视频,不幸的是现在找不到它,但是 mdn has a fairly good description
  • 调试 SW 通常是一种非常烦人的经历,尤其是结合它们怪异的生命周期时
  • 因为它们是如此强大,所以 SW 只能通过 https 提供。无论如何你应该已经在使用 https,但这仍然是一个障碍
  • 为了一个相对较小的利益要做很多事情,所以也许重新考虑它的必要性

(1) 您可以像您一样访问 same origin 中 iframe 的全局 object,但首先要获取您的代码 运行 修改全局 object 确实很棘手。