web-dev-qa-db-ja.com

別の関数が受け取ったXMLHttpRequest応答テキストを変更するにはどうすればよいですか?

変更できない関数が受け取った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
}

また、受信関数k()を操作しようとしました。私の目標を達成するために試みましたが、それは関数に渡すデータに依存しないため(たとえば、k(a.responseText);)、私は成功しませんでした。

これを達成する方法はありますか? jsライブラリ(jQueryなど)を使用したくありません。


[〜#〜] edit [〜#〜]:.responseTextは読み取り専用なので直接変更できないことを理解していますが、応答機能と受信機能の間でコンテンツを変更する方法を見つける。


EDIT2:ここから追加された.responseTextをインターセプトして変更しようとしたメソッドの1つの下に追加されました: Monkey patch 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はグローバルに使用できないことを含めるのを忘れており、どちらも匿名関数(function(){functionsここに})();


EDIT4:AmmarCSEにはjfriend00の回答に関連する問題や複雑さがないため、受け入れられた回答を変更しました。

簡単に説明した最良の答えは次のとおりです。

変更したいリクエストをリッスンします(元の関数の宛先が行う前にリスナーがそれをインターセプトすることを確認してください。そうでない場合、応答がすでに使用された後でそれを変更しても意味がありません)。

元の応答(変更する場合)を一時変数に保存します

変更するプロパティを "書き込み可能:true"に変更します。これにより、プロパティの値がすべて消去されます。私の場合、私は

Object.defineProperty(event, 'responseText', {
    writable: true
});

ここで、eventは、xhrリクエストのloadまたはreadystatechangeイベントをリッスンして返されるオブジェクトです。

これで、応答に必要なものを設定できます。元の応答を変更するだけの場合は、一時変数のデータを使用して、変更を応答に保存できます。

19
Shadow

非常に簡単な回避策の1つは、responseText自体のプロパティ記述子を変更することです

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);

デモ

10
AmmarCSE

編集:以下の2番目のコードオプションを参照してください(テストされ、機能します)。最初のものはいくつかの制限があります。


これらの関数はどれも変更できないため、XMLHttpRequestプロトタイプの後に移動する必要があるようです。ここに1つのアイデアがあります(テストされていませんが、方向を見ることができます):

_(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ハンドラのサルパッチを実行します。次に、パッチを当てた関数は、元のonReadyStateChangeハンドラーが呼び出される前にresponseTextを確認して、別の値を割り当てることができるようにします。

そして最後に、_.responseText_はレディ専用であるため、onreadystatechangeハンドラーを呼び出す前にダミーのXMLHttpResponseオブジェクトを置き換えます。これはすべてのケースで機能するわけではありませんが、onreadystatechangeハンドラーが_this.responseText_を使用して応答を取得する場合は機能します。


そして、XMLHttpRequestオブジェクトを独自のプロキシオブジェクトに再定義する試みを次に示します。これは独自のプロキシオブジェクトであるため、responseTextプロパティを任意に設定できます。 onreadystatechangeを除く他のすべてのプロパティでは、このオブジェクトはget、set、または関数呼び出しを実際の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を使用して高度な方法(バイナリデータ、アップロードなど)をすべて調べ、このプロキシがそれらをすべて機能させるのに十分であることを確認したわけではありません(私はまだそうでない可能性があると思います)それ以上の作業はありませんが、基本的なリクエストに対しては機能しているため、コンセプトは可能です)。


失敗したその他の試み:

  1. XMLHttpRequestオブジェクトから派生させて、コンストラクターを自分のものに置き換えようとしましたが、実際のXMLHttpRequest関数では、派生オブジェクトを初期化する関数として呼び出すことができないため、機能しませんでした。

  2. onreadystatechangeハンドラーをオーバーライドして_.responseText_を変更しようとしましたが、そのフィールドは読み取り専用であるため、変更できません。

  3. thisを呼び出すときにonreadystatechangeオブジェクトとして送信されるダミーオブジェクトを作成しようとしましたが、多くのコードはthisを参照せず、実際のオブジェクトが保存されていますクロージャ内のローカル変数-ダミーオブジェクトを無効にします。

15
jfriend00

リクエストのレスポンスをインターセプトして変更する必要があったので、少しのコードを思いついた。また、一部のWebサイトでは、responseTextと同様に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に一致する正規表現であり、2番目のパラメーターは、応答を変更するために応答で使用される関数です。

使用例

intercept_response(/fruit\.json/i, function(response) {
   var new_response = response.replace('banana', 'Apple');
   return new_response;
});
6
Jake

プロトタイプの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);

上記の関数は、すべての要求の応答テキストを変更します。

1つのリクエストのみを変更する場合は、上記の関数を使用せず、代わりに個々のリクエストでゲッターを定義します。

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();
3
MT0

ChromeクロスOrigin API呼び出しを許可する拡張機能を作成しているときに同じ問題に遭遇しました。これはChromeで機能しました。(更新:最新のChromeバージョン)。

delete _this.responseText;
_this.responseText = "Anything you want";

スニペットは、リクエストを拡張機能のバックグラウンドスクリプトにリダイレクトし、応答時にすべてのプロパティを置き換える、モンキーパッチされたXMLHttpRequest.prototype.send内で実行されます。このような:

// 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またはsendを呼び出すことはできませんでした。私もこのソリューションを試しました: https://stackoverflow.com/a/28513219/3717718 。残念ながら、Safariでも同じエラーが発生しました。

TypeError:構成できないプロパティの構成可能な属性を試行しています。

3
user3717718

リクエストごとに、元の関数が受信する前に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();
3
MT0

ファーストクラスの関数変数は素晴らしいものです! 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を内部的に呼び出します)。

1
CBHacking