Как изменить XMLHttpRequest responsetext, полученный другой функцией?
Я пытаюсь изменить responseText, полученный функцией, которую я не могу изменить. Эта функция создает XMLHttpRequest, к которому я могу прикрепиться, но мне не удалось "обернуть" responseText таким образом, чтобы я мог изменить содержимое до того, как исходная функция получит его.
вот полная исходная функция:
function Mj(a, b, c, d, e) {
function k() {
4 == (m && 'readyState' in m ? m.readyState : 0) && b && ff(b) (m)
}
var m = new XMLHttpRequest;
'onloadend' in m ? m.addEventListener('loadend', k, !1) : m.onreadystatechange = k;
c = ('GET').toUpperCase();
d = d || '';
m.open(c, a, !0);
m.send(d);
return m
}
function ff(a) {
return a && window ? function () {
try {
return a.apply(this, arguments)
} catch(b) {
throw jf(b),
b;
}
} : a
}
Я также попытался манипулировать функцией reiceiving k (); в попытке достичь моей цели, но так как это не зависит от любые данные, передаваемые функции (например, k (a.responseText);) у меня не было успеха.
есть ли способ, которым я могу достичь этого? Я не хочу использовать библиотеки js (такие как jQuery);
редактировать: я понимаю, что не могу измениться .responseText напрямую, так как он доступен только для чтения, но я пытаюсь найти способ изменить содержимое между функцией ответа и получения.
EDIT2: добавлено ниже одного из методы, которые я пытался перехватить и изменить .responseText, который был addapted отсюда:патч обезьяны XMLHTTPRequest.onreadystatechange
(function (open) {
XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
if(/results/.test(url)) {
console.log(this.onreadystatechange);
this.addEventListener("readystatechange", function () {
console.log('readystate: ' + this.readyState);
if(this.responseText !== '') {
this.responseText = this.responseText.split('&')[0];
}
}, false);
}
open.call(this, method, url, async, user, pass);
};
})(XMLHttpRequest.prototype.open);
EDIT3: я забыл включить, что функции Mj и ff не доступны глобально, они оба содержатся внутри анонимной функции (функция () {функции здесь}) ();
EDIT4: я изменил принятый ответ, потому что AmmarCSE делает не имеют никаких проблем и сложности, связанных с ответом jfriend00.
лучший ответ объясняется вкратце следующим образом:
слушайте любой запрос, который вы хотите изменить (убедитесь, что ваш слушатель перехватит его до того, как это сделает исходное назначение функции, иначе нет смысла изменять его после того, как ответ уже был использован).
сохранить оригинальный ответ (если вы хотите изменить его) во временном переменная
измените свойство, которое вы хотите изменить на "writable: true", оно сотрет любое значение, которое у него было. В моем случае я использую
Object.defineProperty(event, 'responseText', {
writable: true
});
здесь event
объект, возвращаемый прослушиванием load
или readystatechange
событие запроса xhr
теперь вы можете установить все, что хотите для своего ответа, если все, что вы хотели, это изменить исходный ответ, то вы можете использовать эти данные из своей временной переменной, а затем Сохранить изменения в ответ.
7 ответов
одним очень простым обходным путем является изменение дескриптора свойства для
Object.defineProperty(wrapped, 'responseText', {
writable: true
});
таким образом, вы можете продлить XMLHttpRequest
как
(function(proxied) {
XMLHttpRequest = function() {
//cannot use apply directly since we want a 'new' version
var wrapped = new(Function.prototype.bind.apply(proxied, arguments));
Object.defineProperty(wrapped, 'responseText', {
writable: true
});
return wrapped;
};
})(XMLHttpRequest);
Edit: см. Второй вариант кода ниже (он протестирован и работает). Первый имеет некоторые ограничения.
поскольку вы не можете изменить ни одну из этих функций, похоже, вам нужно перейти к прототипу XMLHttpRequest. Вот одна идея (непроверенная, но вы можете видеть направление):
(function() {
var open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
var oldReady;
if (async) {
oldReady = this.onreadystatechange;
// override onReadyStateChange
this.onreadystatechange = function() {
if (this.readyState == 4) {
// this.responseText is the ajax result
// create a dummay ajax object so we can modify responseText
var self = this;
var dummy = {};
["statusText", "status", "readyState", "responseType"].forEach(function(item) {
dummy[item] = self[item];
});
dummy.responseText = '{"msg": "Hello"}';
return oldReady.call(dummy);
} else {
// call original onreadystatechange handler
return oldReady.apply(this, arguments);
}
}
}
// call original open method
return open.apply(this, arguments);
}
})();
это делает патч обезьяны для XMLHttpRequest open()
метод, а затем, когда это вызывается для асинхронного запроса, он делает патч обезьяны для обработчик onReadyStateChange, так как это уже должно быть установлено. Эта исправленная функция затем получает возможность видеть responseText перед вызовом исходного обработчика onReadyStateChange, чтобы он мог назначить ему другое значение.
и, наконец, потому что .responseText
только готов, это заменяет фиктивный объект XMLHttpResponse перед вызовом onreadystatechange
обработчик. Это не будет работать во всех случаях, но будет работать, если обработчик onreadystatechange использует this.responseText
получить ответ.
и вот попытка переопределить объект XMLHttpRequest как наш собственный прокси-объект. Поскольку это наш собственный прокси-объект, мы можем установить responseText
собственность на все, что мы хотим. Для всех других свойств, кроме onreadystatechange
, этот объект просто перенаправляет вызов get, set или function реальному объекту XMLHttpRequest.
(function() {
// create XMLHttpRequest proxy object
var oldXMLHttpRequest = XMLHttpRequest;
// define constructor for my proxy object
XMLHttpRequest = function() {
var actual = new oldXMLHttpRequest();
var self = this;
this.onreadystatechange = null;
// this is the actual handler on the real XMLHttpRequest object
actual.onreadystatechange = function() {
if (this.readyState == 4) {
// actual.responseText is the ajax result
// add your own code here to read the real ajax result
// from actual.responseText and then put whatever result you want
// the caller to see in self.responseText
// this next line of code is a dummy line to be replaced
self.responseText = '{"msg": "Hello"}';
}
if (self.onreadystatechange) {
return self.onreadystatechange();
}
};
// add all proxy getters
["status", "statusText", "responseType", "response",
"readyState", "responseXML", "upload"].forEach(function(item) {
Object.defineProperty(self, item, {
get: function() {return actual[item];}
});
});
// add all proxy getters/setters
["ontimeout, timeout", "withCredentials", "onload", "onerror", "onprogress"].forEach(function(item) {
Object.defineProperty(self, item, {
get: function() {return actual[item];},
set: function(val) {actual[item] = val;}
});
});
// add all pure proxy pass-through methods
["addEventListener", "send", "open", "abort", "getAllResponseHeaders",
"getResponseHeader", "overrideMimeType", "setRequestHeader"].forEach(function(item) {
Object.defineProperty(self, item, {
value: function() {return actual[item].apply(actual, arguments);}
});
});
}
})();
рабочая демо:http://jsfiddle.net/jfriend00/jws6g691/
я попробовал это в последние версии IE, Firefox и Chrome, и он работал с простым запросом ajax.
примечание: Я не изучил все расширенные способы, которые Ajax(например, двоичные данные, загрузки и т. д...) можно использовать, чтобы увидеть, что этот прокси достаточно тщателен, чтобы сделать все эти работы (я бы предположил, что это еще не может быть без некоторой дальнейшей работы, но он работает для основных запросов, поэтому похоже, что концепция способна).
другие попытки, не удалось:
пытался получить от объекта XMLHttpRequest, а затем заменить конструктор моим собственным, но это не сработало, потому что реальная функция XMLHttpRequest не позволит вам вызвать ее как функцию для инициализации моего производного объекта.
пытался просто переопределить
onreadystatechange
обработчик и изменение.responseText
, но это поле доступно только для чтения, поэтому вы не можете его изменить.пробовал создать фиктивный объект, который отправлено как
this
объект при вызовеonreadystatechange
, но много кода не ссылкаthis
, но скорее имеет фактический объект, сохраненный в локальной переменной в закрытии - таким образом, победив фиктивный объект.
по запросу я включаю ниже пример фрагмента, показывающий, как изменить ответ XMLHttpRequest, прежде чем исходная функция сможет его получить.
// In this example the sample response should be
// {"data_sample":"data has not been modified"}
// and we will change it into
// {"data_sample":"woops! All data has gone!"}
/*---BEGIN HACK---------------------------------------------------------------*/
// here we will modify the response
function modifyResponse(response) {
var original_response, modified_response;
if (this.readyState === 4) {
// we need to store the original response before any modifications
// because the next step will erase everything it had
original_response = response.target.responseText;
// here we "kill" the response property of this request
// and we set it to writable
Object.defineProperty(this, "responseText", {writable: true});
// now we can make our modifications and save them in our new property
modified_response = JSON.parse(original_response);
modified_response.data_sample = "woops! All data has gone!";
this.responseText = JSON.stringify(modified_response);
}
}
// here we listen to all requests being opened
function openBypass(original_function) {
return function(method, url, async) {
// here we listen to the same request the "original" code made
// before it can listen to it, this guarantees that
// any response it receives will pass through our modifier
// function before reaching the "original" code
this.addEventListener("readystatechange", modifyResponse);
// here we return everything original_function might
// return so nothing breaks
return original_function.apply(this, arguments);
};
}
// here we override the default .open method so that
// we can listen and modify the request before the original function get its
XMLHttpRequest.prototype.open = openBypass(XMLHttpRequest.prototype.open);
// to see the original response just remove/comment the line above
/*---END HACK-----------------------------------------------------------------*/
// here we have the "original" code receiving the responses
// that we want to modify
function logResponse(response) {
if (this.readyState === 4) {
document.write(response.target.responseText);
}
}
// here is a common request
var _request = new XMLHttpRequest();
_request.open("GET", "https://gist.githubusercontent.com/anonymous/c655b533b340791c5d49f67c373f53d2/raw/cb6159a19dca9b55a6c97d3a35a32979ee298085/data.json", true);
_request.addEventListener("readystatechange", logResponse);
_request.send();
вы можете обернуть геттер для responseText
в прототипе с новой функцией и внесите изменения в вывод там.
вот простой пример, который добавляет HTML-комментария <!-- TEST -->
к тексту ответа:
(function(http){
var get = Object.getOwnPropertyDescriptor(
http.prototype,
'responseText'
).get;
Object.defineProperty(
http.prototype,
"responseText",
{
get: function(){ return get.apply( this, arguments ) + "<!-- TEST -->"; }
}
);
})(self.XMLHttpRequest);
вышеуказанная функция изменит текст ответа для всех запросов.
если вы хотите изменить только один запрос, не используйте функцию выше, а просто определите геттер по отдельному запросу вместо этого:
var req = new XMLHttpRequest();
var get = Object.getOwnPropertyDescriptor(
XMLHttpRequest.prototype,
'responseText'
).get;
Object.defineProperty(
req,
"responseText", {
get: function() {
return get.apply(this, arguments) + "<!-- TEST -->";
}
}
);
var url = '/';
req.open('GET', url);
req.addEventListener(
"load",
function(){
console.log(req.responseText);
}
);
req.send();
мне нужно было перехватить и изменить ответ запроса, поэтому я придумал немного кода. Я также обнаружил, что некоторые веб-сайты любят использовать response, а также responseText, поэтому мой код изменяет оба.
Код
var open_prototype = XMLHttpRequest.prototype.open,
intercept_response = function(urlpattern, callback) {
XMLHttpRequest.prototype.open = function() {
arguments['1'].match(urlpattern) && this.addEventListener('readystatechange', function(event) {
if ( this.readyState === 4 ) {
var response = callback(event.target.responseText);
Object.defineProperty(this, 'response', {writable: true});
Object.defineProperty(this, 'responseText', {writable: true});
this.response = this.responseText = response;
}
});
return open_prototype.apply(this, arguments);
};
};
первый параметр функции intercept_response-это регулярное выражение, соответствующее url-адресу запроса, а второй параметр-функция, используемая в ответе для его изменения.
пример Использования
intercept_response(/fruit\.json/i, function(response) {
var new_response = response.replace('banana', 'apple');
return new_response;
});
я столкнулся с той же проблемой, когда я делал расширение Chrome, чтобы разрешить перекрестные вызовы API origin. Это работает в Chrome. (Обновление: он не работает в новейшей версии Chrome).
delete _this.responseText;
_this.responseText = "Anything you want";
фрагмент выполняется внутри monkeypatched XMLHttpRequest.прототип.отправьте, кто перенаправляет запросы в фоновый скрипт расширений и замените все свойства в ответе. Вот так:
// Delete removes the read only restriction
delete _this.response;
_this.response = event.data.response.xhr.response;
delete _this.responseText;
_this.responseText = event.data.response.xhr.responseText;
delete _this.status;
_this.status = event.data.response.xhr.status;
delete _this.statusText;
_this.statusText = event.data.response.xhr.statusText;
delete _this.readyState;
_this.readyState = event.data.response.xhr.readyState;
это не работает в Firefox, но я нашел решение, которое работал:
var test = new XMLHttpRequest();
Object.defineProperty(test, 'responseText', {
configurable: true,
writable: true,
});
test.responseText = "Hey";
это не работает в Chrome, но это работает как в Chrome, так и в Firefox:
var test = new XMLHttpRequest();
var aValue;
Object.defineProperty(test, 'responseText', {
get: function() { return aValue; },
set: function(newValue) { aValue = newValue; },
enumerable: true,
configurable: true
});
test.responseText = "Hey";
последний был копией прошлого из https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
ни одно из решений не работает в Safari. Я попытался создать новый XMLHttpRequest с записываемыми свойствами, но ему не разрешалось вызывать open или отправлять из него. Я также попробовал это решение: https://stackoverflow.com/a/28513219/3717718. К сожалению, это вызвало ту же ошибку в Safari:
ошибку TypeError: попытка настраиваемый атрибут собственность ненастраиваемые.
первоклассные функциональные переменные-замечательные вещи! function f() {a; b; c; }
это то же самое, что и var f = function () {a; b; c; }
Это означает, что вы можете переопределить функции по мере необходимости. Вы хотите обернуть функцию Mj
для возврата измененного объекта? Не проблема. Дело в том, что responseText
поле доступно только для чтения-это боль, но если это единственное поле, вам необходимо...
var Mj_backup = Mj; // Keep a copy of it, unless you want to re-implement it (which you could do)
Mj = function (a, b, c, d, e) { // To wrap the old Mj function, we need its args
var retval = Mj_backup(a,b,c,d,e); // Call the original function, and store its ret value
var retmod; // This is the value you'll actually return. Not a true XHR, just mimics one
retmod.responseText = retval.responseText; // Repeat for any other required properties
return retmod;
}
теперь, когда ваш код страницы вызывает Mj (), он будет вызывать вашу оболочку (которая все равно будет вызывать исходный MJ внутренне, курс.)