Worker using synchronous XMLHttpRequest to get data from GUI - javascript

I'd like a Web Worker which is deep in a call stack to be able to make a synchronous request to get information from the GUI.
The GUI itself is not blocked--it's able to process messages. But the JavaScript on the worker's stack is not written in async / await style. It is just a lot of synchronous code. So if the GUI tried to send a response back to the worker with a postMessage, that would just be stuck in the onmessage() queue.
I've found at least one hack that works in today's browsers. The worker can postMessage to the GUI for the information it wants--along with some sort of ID (e.g. a UUID). Then it can make a synchronous XMLHttpRequest--which is not deprecated on workers--to some server out on the web with that ID.
While the worker is waiting on that http request, the GUI processes the information request. When it's done, it does an XMLHttpRequest to POST to that same server with the ID and the data. The server then uses that information to fulfill the blocking request it is holding open for the worker. This fulfills the synchronous request.
It may seem hare-brained to outsource synchronization between the GUI and the worker to a server. But I'll do it if I have to, because it does not fit the use case to force the worker code to be written in asynchronous style. Also, I'm assuming that someday the browser will be able to do this kind of synchronization natively. But it looks like the one mechanism which could have been used--SharedArrayBuffer, has been disabled for the time being.
UPDATE circa late 2018: SharedArrayBuffer was re-enabled in Chrome for desktop v67. It's not back on for Android Chrome or other browsers yet, and might be a while.
(More bizarre options like compiling a JavaScript interpreter into the worker so the JS stack could be suspended and restarted at will are not on the table--not just due to size and performance, but the inability to debug the worker using the browser's developer tools.)
So...
Is there any way for a synchronous XMLHttpRequest to be fooled into making a request of something coming from within the browser itself (maybe via a custom link scheme?) If the GUI thread could directly answer an XMLHttpRequest that would cut out the middleman.
Could the same functionality be provided via some kind of plugin? I'm thinking maybe synchronization could be done as an abstraction. If someone doesn't have the plugin, it falls back to using the network as a synchronization surrogate. (And presumably if they ever re-enable SharedArrayBuffer, it could just use that.)
I'm wondering also if there is some kind stock JS-ready service which already implements the protocol for the echo server...if anyone knows of one. Seems quite easy to write.

I don't see a way to do what you're trying to do. Approaches that appear initially promising eventually run into hard problems.
Service Workers and fetch
In a comment, you suggested service workers as a possible solution. The service workers examples I've seen mention providing "custom responses to requests". However, all examples use the fetch event to provide the custom response. AFAIK, it is produced only when you actually use the fetch API specifically. An xhr won't generate a fetch event. (Yes, I've tried it and it did not work.) And you cannot just use fetch in your specific situation instead of xhr because fetch does not operate synchronously. The specs for fetch mention a "synchronous flag", but it is not part of the API.
Note that the fetch API and the associated event are not specific to service workers so you could use fetch in a plain worker, or elsewhere, if it solved your problem. You often see fetch mentioned with service workers because service workers can be used for scenarios where regular workers cannot be used and some of those scenarios entail providing custom responses to fetch requests.
Fake XMLHttpRequest
Marinos An suggested in a comment using a fake XMLHttpRequest object. In most cases, that would work. Testing frameworks like Sinon provide fake XMLHttpRequest that allow testing code to have complete control over the responses that the code under test gets. However, it does not work for your use-case scenario. If your fake xhr implementation is implemented as one JavaScript object, and you try sending it to the worker, the worker will get a complete clone of it. Actions on the fake xhr performed inside the worker won't be seen outside the worker. Actions on the fake xhr performed outside the worker won't be seen inside the worker.
It is theoretically possible to work around the cloning issue by having the fake xhr consist of two objects: a front end through which requests are performed, and a backend through which fake responses are established. You could send the front end to the worker, but the front end and the back end would have to communicate with each other and this brings you right back to the communication problem you were trying to solve. If you could make the two parts of the fake xhr talk to each other in a way that allows you to fake synchronous xhr requests, then by the same token you would be able to solve the communication problem without the fake xhr.

Hm...perhaps you could create your workers on the fly, like so
function startNewWorker (code) {
var blob = new Blob([code], {type: "application/javascript"});
var worker = new Worker(URL.createObjectURL(blob));
}
And then, for each new http request you need, you start its own worker:
const w1 = startNewWorker(yourCodeThatDoesSomething);
w1.onmessage = function () { /* ... */};
const w2 = startNewWorker(yourCodeThatDoesSomething);
w2.onmessage = function () { /* ... */};
Both will be asynchronous and will not block the interface for you user, and they both will be able to do their own work, and each of them will have its own listeners.
Notice that code is a string, so, if you have a function, you can use it .toString() concatenated with (), like this:
function myWorkerContent () {
// do something ....
}
const code = "(" + myWorkerContent.toString() + ")()";
// or, if you want to use templateLiterals
// const code = `(${myWorkerContent.toString()})()`;
This way, when running your worker, it will create and execute the function instantly inside each worker of yours.

Related

Is it possible to watch over XHR/Fetch requests happening in the browser?

I want to react to some events that are triggered by a 3rd party on my site.
Those events are Fetch/XHR requests, I'd like to be notified when a "watched" request happens (I assume I would watch them based on the "Request URL"), and I would like to read its request/response headers and payload.
I've never seen that being done before, is it even possible?
The browser is aware of all requests happening, but I'm uncertain whether we can read this, can we?
Eventually, I hope to achieve a watcher that would trigger my own code when some endpoints have been called, while re-using the data sent by those requests. I'm hoping to both detect WHEN a particular endpoint is called, and WHAT the response is, so that I can use it for my own needs (without needing to perform yet another identical request)
I'm not sure if reading the data will be possible, I found out that reacting to fetch requests is possible with the help of Resource Timing (see https://www.w3.org/TR/resource-timing/)
For example, you can create decorator-function for fetch:
const origFetch = fetch;
window.fetch = function(...args) {
console.log(args);
return origFetch.apply(this, args);
}
Also you can create decorator for Response.prototype.json function.
It's possible using the WebRequest built-in library. The following example is specific to the Chrome Extension runtime, but something similar is doable for browsers, too.
For Chrome Extensions, it needs to run as a Service Worker (e.g: background.js)*
chrome.webRequest.onBeforeSendHeaders.addListener(
(requestHeadersDetails) => {
console.log('requestHeadersDetails', requestHeadersDetails);
},
{
urls: ['https://*'],
},
[
'requestHeaders',
'extraHeaders',
],
);
See https://developer.chrome.com/docs/extensions/reference/webRequest/#event-onBeforeRequest and https://developer.chrome.com/docs/extensions/reference/webRequest/#event-onBeforeSendHeaders (both are very similar)
I didn't find a way to re-use the data that were fetched, though.
But at least I'm notified when a request is sent.

How do I get around Angular undoing my monkey patching with its own monkey patching?

I'd like to intercept XHR requests for the Google Maps API so I can run them through my own proxy server as a way of keeping my API key private.
Angular has its own HttpInterceptors, but they'll only intercept XHR requests which are made using Angular's HttpClient, not any requests made outside of the Angular framework by the Maps API. I'd think that monkey patching XMLHttpRequest.open() would be the best way to get at the requests going to the Maps API, which I've done like this:
var oldXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
console.log(url);
return oldXHROpen.apply(this, arguments);
};
The above code is placed in a <script> in the <head> section of my index.html, so it's definitely executed before any Angular code is executed.
The patch works... for a short time. I see URLs for a few assets loaded by my code logged out, but then this message appears:
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
After that just one more URL gets logged, and that's the last intercept I make. XHR requests continue to be processed, but my patch never sees them happen.
I'm sure this has something to do with zone.js, but I still don't understand how it could happen. Since I redefine XMLHttpRequest.prototype.open before Angular or zone.js even get a chance to see the original open() function, which is tucked away in the oldXHROpen variable, how does a direct connection to the native open() ever manage to happen again, bypassing my patch?
It appears I was laboring under the false premise that the client side of the Google Maps API would have to make XHR requests to get its work done.
It doesn't. It's all done by loading images, CSS, and fonts, with a little JSONP thrown in.
My monkey patching actually does work, and doesn't get undone by Angular.

How can I remove a buggy service worker, or implement a "kill switch"?

I'm playing with the service worker API in my computer so I can grasp how can I benefit from it in my real world apps.
I came across a weird situation where I registered a service worker which intercepts fetch event so it can check its cache for requested content before sending a request to the origin.
The problem is that this code has an error which prevented the function from making the request, so my page is left blank; nothing happens.
As the service worker has been registered, the second time I load the page it intercepts the very first request (the one which loads the HTML). Because I have this bug, that fetch event fails, it never requests the HTML and all I see its a blank page.
In this situation, the only way I know to remove the bad service worker script is through chrome://serviceworker-internals/ console.
If this error gets to a live website, which is the best way to solve it?
Thanks!
I wanted to expand on some of the other answers here, and approach this from the point of view of "what strategies can I use when rolling out a service worker to production to ensure that I can make any needed changes"? Those changes might include fixing any minor bugs that you discover in production, or it might (but hopefully doesn't) include neutralizing the service worker due to an insurmountable bug—a so called "kill switch".
For the purposes of this answer, let's assume you call
navigator.serviceWorker.register('service-worker.js');
on your pages, meaning your service worker JavaScript resource is service-worker.js. (See below if you're not sure the exact service worker URL that was used—perhaps because you added a hash or versioning info to the service worker script.)
The question boils down to how you go about resolving the initial issue in your service-worker.js code. If it's a small bug fix, then you can obviously just make the change and redeploy your service-worker.js to your hosting environment. If there's no obvious bug fix, and you don't want to leave your users running the buggy service worker code while you take the time to work out a solution, it's a good idea to keep a simple, no-op service-worker.js handy, like the following:
// A simple, no-op service worker that takes immediate control.
self.addEventListener('install', () => {
// Skip over the "waiting" lifecycle state, to ensure that our
// new service worker is activated immediately, even if there's
// another tab open controlled by our older service worker code.
self.skipWaiting();
});
/*
self.addEventListener('activate', () => {
// Optional: Get a list of all the current open windows/tabs under
// our service worker's control, and force them to reload.
// This can "unbreak" any open windows/tabs as soon as the new
// service worker activates, rather than users having to manually reload.
self.clients.matchAll({type: 'window'}).then(windowClients => {
windowClients.forEach(windowClient => {
windowClient.navigate(windowClient.url);
});
});
});
*/
That should be all your no-op service-worker.js needs to contain. Because there's no fetch handler registered, all navigation and resource requests from controlled pages will end up going directly against the network, effectively giving you the same behavior you'd get without if there were no service worker at all.
Additional steps
It's possible to go further, and forcibly delete everything stored using the Cache Storage API, or to explicitly unregister the service worker entirely. For most common cases, that's probably going to be overkill, and following the above recommendations should be sufficient to get you in a state where your current users get the expected behavior, and you're ready to redeploy updates once you've fixed your bugs. There is some degree of overhead involved with starting up even a no-op service worker, so you can go the route of unregistering the service worker if you have no plans to redeploy meaningful service worker code.
If you're already in a situation in which you're serving service-worker.js with HTTP caching directives giving it a lifetime that's longer than your users can wait for, keep in mind that a Shift + Reload on desktop browsers will force the page to reload outside of service worker control. Not every user will know how to do this, and it's not possible on mobile devices, though. So don't rely on Shift + Reload as a viable rollback plan.
What if you don't know the service worker URL?
The information above assumes that you know what the service worker URL is—service-worker.js, sw.js, or something else that's effectively constant. But what if you included some sort of versioning or hash information in your service worker script, like service-worker.abcd1234.js?
First of all, try to avoid this in the future—it's against best practices. But if you've already deployed a number of versioned service worker URLs already and you need to disable things for all users, regardless of which URL they might have registered, there is a way out.
Every time a browser makes a request for a service worker script, regardless of whether it's an initial registration or an update check, it will set an HTTP request header called Service-Worker:.
Assuming you have full control over your backend HTTP server, you can check incoming requests for the presence of this Service-Worker: header, and always respond with your no-op service worker script response, regardless of what the request URL is.
The specifics of configuring your web server to do this will vary from server to server.
The Clear-Site-Data: response header
A final note: some browsers will automatically clear out specific data and potentially unregister service workers when a special HTTP response header is returned as part of any response: Clear-Site-Data:.
Setting this header can be helpful when trying to recover from a bad service worker deployment, and kill-switch scenarios are included in the feature's specification as an example use case.
It's important to check the browser support story for Clear-Site-Data: before your rely solely on it as a kill-switch. As of July 2019, it's not supported in 100% of the browsers that support service workers, so at the moment, it's safest to use Clear-Site-Data: along with the techniques mentioned above if you're concerned about recovering from a faulty service worker in all browsers.
You can 'unregister' the service worker using javascript.
Here is an example:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
//returns installed service workers
if (registrations.length) {
for(let registration of registrations) {
registration.unregister();
}
}
});
}
That's a really nasty situation, that hopefully won't happen to you in production.
In that case, if you don't want to go through the developer tools of the different browsers, chrome://serviceworker-internals/ for blink based browsers, or about:serviceworkers (about:debugging#workers in the future) in Firefox, there are two things that come to my mind:
Use the serviceworker update mechanism. Your user agent will check if there is any change on the worker registered, will fetch it and will go through the activate phase again. So potentially you can change the serviceworker script, fix (purge caches, etc) any weird situation and continue working. The only downside is you will need to wait until the browser updates the worker that could be 1 day.
Add some kind of kill switch to your worker. Having a special url where you can point users to visit that can restore the status of your caches, etc.
I'm not sure if clearing your browser data will remove the worker, so that could be another option.
I haven't tested this, but there is an unregister() and an update() method on the ServiceWorkerRegistration object. you can get this from the navigator.serviceWorker.
navigator.serviceWorker.getRegistration('/').then(function(registration) {
registration.update();
});
update should then immediately check if there is a new serviceworker and if so install it. This bypasses the 24 hour waiting period and will download the serviceworker.js every time this javascript is encountered.
For live situations you need to alter the service worker at byte-level (put a comment on the first line, for instance) and it will be updated in the next 24 hours. You can emulate this with the chrome://serviceworker-internals/ in Chrome by clicking on Update button.
This should work even for situations when the service worker itself got cached as the step 9 of the update algorithm set a flag to bypass the service worker.
We had moved a site from godaddy.com to a regular WordPress install. Client (not us) had a serviceworker file (sw.js) cached into all their browsers which completely messed things up. Our site, a normal WordPress site, has no service workers.
It's like a virus, in that it's on every page, it does not come from our server and there is no way to get rid of it easily.
We made a new empty file called sw.js on the root of the server, then added the following to every page on the site.
<script>
if (navigator && navigator.serviceWorker && navigator.serviceWorker.getRegistration) {
navigator.serviceWorker.getRegistration('/').then(function(registration) {
if (registration) {
registration.update();
registration.unregister();
}
});
}
</script>
In case it helps someone else, I was trying to kill off service workers that were running in browsers that had hit a production site that used to register them.
I solved it by publishing a service-worker.js that contained just this:
self.globalThis.registration.unregister();

Track XMLHTTPRequest results

I have a system built of many modules that use AJAX to do POSTs and GETs. If I monitor the results of these requests, I can know if the system is responsive - ie if the browser is still connected to the IP source.
I can by hand inject some callback methods in jQuery's AJAX .fail(). I have actually done this but it can be easy to forget and it adds a lot of extra code, as everything about this system requires AJAX.
I saw this interesting code to intercept XMLHTTPRequest open prototype method
(function(open) {
XMLHttpRequest.prototype.open = function() {
this.addEventListener("readystatechange", function() {
console.log(this.readyState);
}, false);
open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);
I looked through the api, https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest, but I did not find somewhere that I could intercept success/failed/timed out requests. Is this possible?
It's all about listening to the appropriate events, they appear in the left sidebar in your link under "Events".
That said, I would consider using / taking some inspiration of zone.js.
A Zone is an execution context that persists across async tasks. You can think of it as thread-local storage for JavaScript VMs.

AJAX fire-and-forget, looking for the opposite of server-sent event

Is these an API symmetric to Server-Sent Event to generate fire-and-forget events from browser to server? I know how to not reply to a request on the server side, but how to tell the browser that it does not need to wait for a reply?
The goal here is to save resources on the client side, say you want to send 10k events to the server as fast as possible, not caring about what the sever replies.
Edit: While mostly irrelevant to the question, here is some background about the project I'm working on which would make use of an "AJAX fire-and-forget". I want to build a JavaScript networking library for Scala.js that will have as one of its applications to be the transport layer between Akka actors on the JVM and on a browser (compiled with Scala.js). When WebSockets are not available I want to have some sort of fallback, and having a pending connection for the duration of a round trip on each JS->JVM message is not acceptable.
As you have asked for "how to tell the browser that it does not need to wait for a reply?"
I assume that you do not want to process the server reply.
in such case, it is better to utilize one pixel image response trick which is implemented by Google for analytics and tracking, and many other such services.
More details here
The trick is to create new image using javascript and set src property, the browser will immediately fire the request for image and browser can parallelly request form multiple such requests.
var image = new Image();
image.src = "your-script.php?id=123&other_params=also";
PROs:
easy to implement
less load on server/client, then ajax request
CONs:
you can send only GET requests using this appproach.
Edit
For more references:
http://help.yahoo.com/l/us/yahoo/ywa/faqs/tracking/advtrack/3520294.html
https://support.google.com/dfp_premium/answer/1347585?hl=en
How to create and implement a pixel tracking code
Again they are using same technique of pixel image.
So, just to be clear, you're trying to use the XMLHttpRequest as a proxy for your network communication, which means you are 100% at the mercy of whatever XMLHttpRequest offers you, right?
My take is that if you're going to stick with XMLHttpRequest for this, you're going to have to just make peace with getting a server response. Just make the call asynchronously and have the response handled by a no-op function. Consider what somebody else suggested, using a queue on the server (or an asynchronous method on the server) so you return immediately to the client. Otherwise, I really think JavaScript is just the wrong tool for the job you're describing.
XMLHttpRequest is going to be a different implementation (presenting a more or less common interface contract) in every browser. I mean, Microsoft invented the thing, then the other browser makers emulated it, then voila, everybody started calling it Web 2.0. Point being, if you push too hard at the doughy center of XMLHttpRequest, you may get different behavior in different browsers.
XMLHttpRequest, as far as I know, strictly uses TCP (no UDP option), so at the very least your client is going to receive a TCP ACK from the server. There is no way to tell the server not to respond at that level. It's baked into the TCP/IP network stack.
Additionally, the communication uses the HTTP protocol, so the server will respond with HTTP headers... right? I mean, that is simply the way the protocol is defined. Telling HTTP to be something different is kind of like telling a cat to bark like a chicken.
Even if you could cancel the request on the client side by calling abort() on XMLHttpRequest, you're not cancelling it on the server side. To do so, even if it were possible with XMLHttpRequest, would require an additional request sent all the way to the server to tell it to cancel the response to the preceding request. How does it know which response to cancel? You'd have to manage request id's of some kind. You would have to be resilient to out-of-order cancellation requests. Complicated.
So here's a thought (I'm just thinking out loud): Microsoft's XMLHttpRequest was based at least in spirit on an even earlier Microsoft technology from the Visual Interdev days, which used a Java applet on the client to asynchronously fire off a request to the server, then it would pass control to your preferred JavaScript callback function when the response showed up, etc. Pretty familiar.
That Java async request thing got skewered during the whole Sun vs. Microsoft lawsuit fiasco. I heard rumors that a certain original Microsoft CEO would blow a gasket any time he learned about Microsoft tech being implemented using Java, and kill the tech. Who knows? I was unhappy when that capability disappeared for a couple of years, then happy again when XMLHttpRequest eventually showed up.
Maybe you see where I'm going, here... :-)
I think perhaps you're trying to squeeze behavior out of XMLHttpRequest that it just isn't built for.
The answer might be to just write your own Java applet, do some socket programming and have it do the kind communications you want to see from it. But then, of course, you'll have issues with people not having Java enabled in their browsers, exacerbated by all the recent Java security problems. So you're looking at code-signing certificates and so on. And you're also looking at issues that you'll need to resolve on the server side. If you still use HTTP and work through your web server, the web server will still want to send HTTP responses, which will still tie up resources on the server. You could make those actions on the server asynchronous so that TCP sockets don't stay tied up longer than necessary, but you're still tying up resources on the server side.
I managed to get the expected behavior using a very small timeout of 2ms. The following call is visible by the server but the connection is closed on the client side before any reply from the server:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 2) {
alert("Response header recived, it was not a fire-and-forget...");
}
};
xhr.open("POST", "http://www.service.org/myService.svc/Method", true);
xhr.timeout = 2;
xhr.send(null);
This is not fully satisfactory because the timeout may change between browser/computers (for instance, 1ms does not work on my setup). Using a large timeout in the order of 50ms means that the client might hit the limit of maximum concurrent opened connections (6 on my setup).
Using XMLHttpRequest to send an async request (i.e. where you don't care if it succeeds or what the response is:
var req = new XMLHttpRequest();
req.open('GET', 'http://my.url.goes.here.com');
req.send();
You can do much the same thing with an Image object, too, btw:
new Image().src = 'http://my.url.goes.here.com';
The Image approach works particularly well if you're making cross-domain requests, since Images aren't subject to same-origin security restrictions the way XHR requests are. (BTW, it's good practice but not essential to have your endpoint return a 1x1 pixel PNG or GIF response with the appropriate Content-Type, to avoid browser console warnings like 'Resource interpreted as Image but transferred with MIME type text/html'.)
It sounds like you're trying to solve the wrong problem. Instead of dealing with this on the client, why not handle this on the server side.
Take the message from the client and put a message on a service bus or store the data in a database and return to the client. Depending on your stack and architecture, this should be fairly simple and very fast. You can process the message out of band, either a second service listens to the message bus and processes the request, or some sort of batch processor can come along later and process the records in the database.
You won't have the same level of fine-grained control of the connection with XHR as with WebSockets. Ultimately, it's the browser that manages the HTTP connection lifecycle.
Instead of falling back from WebSockets to discrete XHR connections, maybe you can store and batch your events. For instance:
Client JS
function sendMessage(message) {
WebSocketsAvailable ? sendWithWebSockets(message) : sendWithXHR(message);
}
var xhrQueue = [];
function sendWithXHR(message) {
xhrQueue.push({
timestamp: Date.now(), // if this matters
message: message
});
}
function flushXhrQueue() {
if (xhrQueue.length) {
var req = new XMLHttpRequest();
req.open('POST', 'http://...');
req.onload = function() { setTimeout(flushXhrQueue, 5000); };
// todo: needs to handle errors, too
req.send(JSON.stringify(xhrQueue));
xhrQueue = [];
}
else {
setTimeout(flushXhrQueue, 5000);
}
}
setTimeout(flushXhrQueue, 5000);
On the server, maybe you can have two endpoints: one for WebSockets and one for XHR. The XHR handler deserialises the JSON queue object and calls (once per message) the same handler used by the WebSockets handler.
Server pseudo-code
function WSHandler(message) {
handleMessage(message, Date.now());
}
function XHRHandler(jsonString) {
var messages = JSON.parse(jsonString);
for (var messageObj in messages) {
handleMessage(messageObj.message, messageObj.timestamp);
}
}
function handleMessage(message, timestamp) {
...
}

Categories