Перехватывает 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
Другие вопросы по тегам