如何在 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 确实很棘手。
我有一个页面通常会用包装器覆盖 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 确实很棘手。