Перехватывает XMLHttpRequest и изменяет responseText
Я пытаюсь создать сценарий, который будет выступать в качестве прокси / оболочки для нативного XMLHttpRequest
объект, позволяющий мне перехватить его, изменить responseText и вернуться к исходному событию onreadystatechange.
В контексте, если данные, которые приложение пытается получить, уже доступны в локальном хранилище, для отмены XMLHttpRequest
и передать локально сохраненные данные обратно в методы обратного вызова для успешного / неудачного выполнения приложений. Предположим, я не имею никакого контроля над существующими методами обратного вызова AJAX.
Первоначально я попробовал следующую идею..
var send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(data){
//Do some stuff in here to modify the responseText
send.call(this, data);
};
Но, как я уже установил, текст ответа доступен только для чтения.
Затем я попытался сделать шаг назад, написав свой собственный полный прокси XMLHttpRequest
В итоге я написал свою собственную версию нативных методов. Подобно тому, что обсуждается здесь...
http://www.ilinsky.com/articles/XMLHttpRequest/
Но это быстро запутало, и все еще трудно вернуть измененные данные обратно в оригинал. onReadyStateChange
метод.
Какие-либо предложения? Это вообще возможно?
4 ответа
//
// firefox, ie8+
//
var accessor = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'responseText');
Object.defineProperty(XMLHttpRequest.prototype, 'responseText', {
get: function() {
console.log('get responseText');
return accessor.get.call(this);
},
set: function(str) {
console.log('set responseText: %s', str);
//return accessor.set.call(this, str);
},
configurable: true
});
//
// chrome, safari (accessor == null)
//
var rawOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
if (!this._hooked) {
this._hooked = true;
setupHook(this);
}
rawOpen.apply(this, arguments);
}
function setupHook(xhr) {
function getter() {
console.log('get responseText');
delete xhr.responseText;
var ret = xhr.responseText;
setup();
return ret;
}
function setter(str) {
console.log('set responseText: %s', str);
}
function setup() {
Object.defineProperty(xhr, 'responseText', {
get: getter,
set: setter,
configurable: true
});
}
setup();
}
Следующий скрипт отлично перехватывает данные перед отправкой через XMLHttpRequest.prototype.send
<script>
(function(send) {
XMLHttpRequest.prototype.send = function(data) {
this.addEventListener('readystatechange', function() {
}, false);
console.log(data);
alert(data);
};
})(XMLHttpRequest.prototype.send);
</script>
Ваш шаг назад является излишним: вы можете добавить свой собственный метод получения в XMLHttpRequest: ( подробнее о свойствах)
Object.defineProperty(XMLHttpRequest.prototype,"myResponse",{
get: function() {
return this.responseText+"my update"; // anything you want
}
});
Использование:
var xhr = new XMLHttpRequest();
...
console.log(xhr.myResponse); // xhr.responseText+"my update"
Обратите внимание, что в современных браузерах вы можете запустить xhr.onload
(см. советы по XMLHttpRequest2)
По моему мнению, чтобы перехватить ответ, более современным решением будет расширение исходного XMLHttpRequest и перезапись его вwindow
объект:
const { interceptXhrResponse } = (function () {
let interceptionRules = [];
/**
* Function to intercept responses for given URL patterns
* @param {RegExp} urlPattern - Regular expression to match the (canonicalized) URL
* @param {Function} responseHandler - Function to handle the intercepted response
*/
function interceptXhrResponse(urlPattern, responseHandler) {
interceptionRules.push({ urlPattern, responseHandler });
}
// Function to find specific handler for the URL and return modified response
function handleInterceptedResponse(response, url) {
const interceptionRule = interceptionRules.find(({ urlPattern }) =>
urlPattern.test(url)
);
if (interceptionRule) {
const { responseHandler } = interceptionRule;
return responseHandler(response, url);
}
return response;
}
const OriginalXMLHttpRequest = window.XMLHttpRequest;
class XMLHttpRequest extends OriginalXMLHttpRequest {
get responseText() {
// If the request is not done, return the original responseText
if (this.readyState !== 4) {
return super.responseText;
}
return handleInterceptedResponse(super.responseText, this.responseURL);
}
get response() {
// If the request is not done, return the original response
if (this.readyState !== 4) {
return super.response;
}
return handleInterceptedResponse(super.response, this.responseURL);
}
}
window.XMLHttpRequest = XMLHttpRequest;
return { interceptXhrResponse };
})();
Верхний код раскрываетinterceptXhrResponse
функция, позволяющая указать шаблон URL-адреса с помощью регулярного выражения и соответствующего обработчика ответа. Вы можете вернуть в обработчике все, что хотите, чтобы изменить ответ.
Например:
interceptXhrResponse(/.+/, (response, url) => {
return `Response of ${url}: Intercepted. Original response length: ${String(response).length}`
})
Тогда мы можем попытаться инициироватьXMLHttpRequest
:
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://stackoverflow.com/404')
xhr.send()
xhr.onloadend = () => {
console.log(xhr.responseText)
}
Выход:
Response of https://stackoverflow.com/404: Intercepted. Original response length: 63486