Background and Thinking

Why do I need to take over XHR requests? This requires us to understand some of its application scenarios. How do we unify the behavior of XHR requests in our project, monitor the entire lifecycle of requests, customize intercepting requests and returning mock data, create a fully controlled console (like vConsole), and more!

There is one of the most common situations. For example, there are different ways to initiate requests in projects, including JS SDK or private NPM library, third-party JS CDN, ajax and AXIOS. What if we need to add some uniform behavior to all requests in the project?


Review of native XMLHttpRequest

Initiate requests using XHR

Note: The following is only for XHR processing, not considering the use of ActiveXObject to handle compatibility, not considering the use of FETCH requests.

Var XHR = new XMLHttpRequest (); Xhr. open(method, url, async, username, password); Xhr.setrequestheader ('customId', 666) // For asynchronous requests, Xhr.onreadystatechange = function () {// Listen for readyState and HTTP status if (xhr.readyState == 4&&xhr.status == 200) { console.log(xhr.responseText); // Use the send() method to send the request xhr.send(body); // For synchronous requests, the data console.log(xhr.responsetext) can be received directly; Xhr.onreadystatechange = function () {}; // Clean up the event response function (IE, Firefox compatibility) xhr.abort();Copy the code

ES5 implements local interception

Suppose you use Ajax, AXIos, and native XHR to add a custom request header custom-trace-id:’ AA, BB ‘to the request. How do we get the value by intercepting and adding two new headers’ custom-A ‘: ‘AA’ and ‘custom-b’: ‘bb’?

Intercepting all XHR in the project and adding new custom request headers for ‘custom-trace’ headers (intercepting only open and setRequestHeader)

(function(w){w.rewritexhr = {// generate uuid _setUUID: function () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }, / / storage XHR prototype xhrProto: w.X MLHttpRequest. Prototype, / / store need to intercept the local properties or methods tempXhrProto: The function () {this. _open = this. XhrProto. Open enclosing _setRequestHeader = this. XhrProto. SetRequestHeader}, / / intercept processing proxy: function(){ var _this = this this.xhrProto.open = function () { this._open_args = [...arguments]; return _this._open.apply(this, arguments); } / / intercept this setRequestHeader method. XhrProto. SetRequestHeader = function () {var headerKey = the arguments [0] / / need to all requests to increase the head of the var  keys = ['custom-a', 'custom-b', Open_args [1] if(/^custom-trace-id$/.test(headerKey)){if(/^custom-trace-id$/.test(headerKey)){ var value = arguments[1] && arguments[1].split(',') value[2] = _this._setUUID() keys.forEach((key, _setRequestHeader. Apply (this, arguments) this.setrequestheader (key, arguments) value[index]) }) return } return _this._setRequestHeader.apply(this, arguments) } }, init: function(){ this.tempXhrProto() this.proxy() } } w.rewriteXhr.init() })(window) </script>Copy the code

Above, we redefined the prototype methods of open and setRequestHeader (the purpose of intercepting open is to only get the URL and other information in the method’s parameters), while also storing the original Open and setRequestHeader. Every time a request is called to setRequestHeader, the actual call is our own rewrite of setRequestHeader method, in this method to call the original setRequestHeader, so as to achieve the purpose of intercepting the setRequestHeader.

Knowing about local XHR interception, we can use this to think about how to encapsulate and implement global request interception.


ES5 implements global interception

Use xhrHook in your project

xhrHook({ open: function (args, xhr) { console.log("open called!" , args, xhr) }, setRequestHeader: function (args, xhr) { console.log("setRequestHeader called!" ResponseText = xhr.responsetext. Replace (' ABC ', '')}})Copy the code

The realization of the xhrHook

In global interception, we need to take account of instance properties, methods, and event handling.

Function xhrHook(config){// Store the real XHR constructor. Recoverable window. RealXhr = window. RealXhr | | XMLHttpRequest / / rewrite XMLHttpRequest constructor XMLHttpRequest = function () {var XHR = new Window.realxhr () // Iterate over the instance and its attributes on the stereotype (if the instance and the stereotype have the same attributes on the chain, Take for instance attributes) (var attr XHR) in {if (Object. The prototype. ToString. Call (a) = = = '[Object Function]') {this [attr] = hookFunction(attr); } else {object.defineProperty (this, attr, {getterFactory(attr), set: SetterFactory (attr), Enumerable: true})}} This.xhr = XHR} </script>Copy the code

Method interception in XHR

// method interception in XHR, eg: Open, send etc. Function hookFunction (fun) {return function () {var args = Array. Prototype. Slice. The call (the arguments) / / Save the open parameter to XHR, which is available in other event callbacks. If (fun === 'open'){this.xhr.open_args = args} if(config[fun]) {// Stop calling var result = if the result of the configured function returns true config[fun].call(this, args, this.xhr) if (result) return result; } return this.xhr[fun].apply(this.xhr, args); }}Copy the code

Interception of attributes and events in XHR

/ / attributes and callback methods interception function getterFactory () {var value = this. XHR [attr] var getter = (proxy [attr] | | {}) "getter" return getterHook && getterHook(value, This) | | value} / / in the assignment is triggered when the plant functions (such as onload event) function setterFactory attr () {return function (value) {var XHR = this. XHR;  var _this = this; var hook = config[attr]; If (/^on/.test(attr)) {XHR [attr] = function (e) {e = configEvent(e, _this) var result = hook && hook.call(_this, xhr, e) result || value.call(_this, e); } } else { var attrSetterHook = (hook || {})["setter"] value = attrSetterHook && attrSetterHook(value, _this) || value try { xhr[attr] = value; {} the catch (e) the console. Warn (' XHR '+ attr +' attributes do not write ')}}}}Copy the code

Remove XHR interception and return XHR management rights

Function unXhrHook() {if (window[realXhr]) XMLHttpRequest = window[realXhr]; window[realXhr] = undefined; }Copy the code

ES6 implements global interception

The night has been deep, waiting for sorting……


conclusion

The global interception of XHR is generally simple, except that the process of managing events is a bit complicated. The common feature of both local and global processing is to store native XHR, but to execute native properties, methods, and events, you first execute your own handler, perform some operations in the function, and then execute the native method.

For instance, when we define xhr.onload = function(){}, we actually trigger our own setter method for onload, which will bind the actual XHR callback function onload, If config.onload() does not return or returns false, the xhr.onload function that was previously bound outside will continue to be executed.

If you have any inadequacies, questions or suggestions, please leave comments and point them out.