简体   繁体   中英

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

I have a page which will normally overrides window.XMLHttpRequest with a wrapper that does a few extra things like inserting in headers on certain requests.

I have some functionality in a 3rd party library that uses HTML5 Worker, and we are seeing that this request does not use the XMLHttpRequest wrapper object. So any request that this library makes is missing the required headers, and so the request will fail.

Is there a way to control the XMLHttpRequest that any Worker the current thread creates?

This 3rd party library code looks like this:

        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;
        }

The Javascript that is returned by the URL variable above contains code like this:

        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)
        }
        )

The answer is both a soft "no" and an eventual "yes".

When a piece of code runs in a different context (like a webworker or an iframe), you do not have direct control of its global object (1).

What's more, XMLHttpRequest isn't the only way to send out network requests - you have several other methods, chief among them the fetch api .

However, there's a relatively new kid in block called Service Workers which can help you quite a bit!

Service workers

Service workers (abbrev. SWs) are very much like the web workers you already know, but instead of only running in the current page, they continue to run in the background as long as your user stays in your domain. They are also global to your entire domain , so any request made from your site will be passed through them.

Their main purpose in life is reacting to network requests, usually used for caching purposes and offline content, serving push notifications, and several other niche uses.

Let's see a small example (note, run these from a local webserver):

// 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);
})

Here we're registering a service worker and then requesting a page every 5 seconds. In the service worker, we're simple logging each network event, which can be caught in the fetch event.

On first load, you should see the service worker being registered. SWs only begin intercepting requests from the first page after they were installed...so refresh the page to begin seeing the fetch events being logged. I advise you to play around with the event properties before reading on so things will be clearer.

Cool! We can see from poking around with the event in the console that event.request is the Request object our browser constructed. In an ideal world, we could access event.request.headers and add our own headers! Dreamy, isn't it!?

Unfortunately, request/response headers are guarded and immutable . Fortunately, we are a stubborn bunch and can simply re-construct the request:

// 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);
});

This is a few different concepts at play so it's okay if it takes more than once to get. Essentially though, we're creating a new request which will be sent in place of the original one, and setting a new header! Hurray!

在此输入图像描述

The Bad

Now, to some of the downsides:

  • Since we're hijacking every single request , we can accidentally change requests we didn't mean to and potentially destroy the entire universe!
  • Upgrading SWs is a huge pain. SW lifecycle is complex, debugging it on your users is difficult. I've seen a good video on dealing with it, unfortunately can't find it right now, but mdn has a fairly good description
  • Debugging SWs is often a very annoying experience, especially when combined with their weird lifecycles
  • Because they are so powerful, SWs can only be served over https. You should already be using https anyway, but this is still a hindrance
  • This is a lot of things to do for a relatively small benefit, so maybe reconsider its necessity

(1) You can access the global object of an iframe in the same origin as you, but getting your code to run first to modify the global object is tricky indeed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM