Dodaj „hak” do wszystkich żądań AJAX na stronie


104

Chciałbym wiedzieć, czy możliwe jest „podpięcie” się do każdego pojedynczego żądania AJAX (w momencie, gdy ma zostać wysłane lub w przypadku zdarzeń) i wykonać akcję. W tym momencie zakładam, że na stronie znajdują się inne skrypty innych firm. Niektóre z nich mogą używać jQuery, a inne nie. czy to możliwe?


Jest to możliwe z jQuery, więc jest to możliwe za pomocą zwykłego starego javascript, ale musisz mieć co najmniej 2 "hooki" dla każdego z nich. W każdym razie, po co używać obu na tej samej stronie?
yoda

2
Co powiesz na korzystanie z tej biblioteki? github.com/slorber/ajax-interceptor
Sebastien Lorber


2
Uwaga: odpowiedzi na to pytanie nie obejmują wywołań Ajax wykonywanych z nowszego fetch()interfejsu API w nowoczesnych przeglądarkach.
jfriend00

Odpowiedzi:


110

Zainspirowany odpowiedzią Aviva , przeprowadziłem małe dochodzenie i oto, co wymyśliłem.
Nie jestem pewien, czy to wszystko jest tak przydatne, jak w komentarzach w skrypcie i oczywiście będzie działać tylko w przeglądarkach używających natywnego obiektu XMLHttpRequest .
Myślę, że zadziała, jeśli biblioteki javascript są używane, ponieważ będą używać obiektu natywnego, jeśli to możliwe.

function addXMLRequestCallback(callback){
    var oldSend, i;
    if( XMLHttpRequest.callbacks ) {
        // we've already overridden send() so just add the callback
        XMLHttpRequest.callbacks.push( callback );
    } else {
        // create a callback queue
        XMLHttpRequest.callbacks = [callback];
        // store the native send()
        oldSend = XMLHttpRequest.prototype.send;
        // override the native send()
        XMLHttpRequest.prototype.send = function(){
            // process the callback queue
            // the xhr instance is passed into each callback but seems pretty useless
            // you can't tell what its destination is or call abort() without an error
            // so only really good for logging that a request has happened
            // I could be wrong, I hope so...
            // EDIT: I suppose you could override the onreadystatechange handler though
            for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) {
                XMLHttpRequest.callbacks[i]( this );
            }
            // call the native send()
            oldSend.apply(this, arguments);
        }
    }
}

// e.g.
addXMLRequestCallback( function( xhr ) {
    console.log( xhr.responseText ); // (an empty string)
});
addXMLRequestCallback( function( xhr ) {
    console.dir( xhr ); // have a look if there is anything useful here
});

byłoby miło rozszerzyć tę odpowiedź na obsługę hooków post-request
Sebastien Lorber

1
W oparciu o twoją implementację opublikowałem coś w NPM, które działa zarówno z żądaniami, jak i odpowiedziami! github.com/slorber/ajax-interceptor
Sebastien Lorber

4
console.log (xhr.responseText) jest pustym ciągiem, ponieważ w tym momencie xhr jest pusty. jeśli przekażesz zmienną xhr do zmiennej globalnej i ustawisz opóźnienie o kilka sekund, będziesz mógł uzyskać bezpośredni dostęp do właściwości
Toolkit

5
niezła odpowiedź. Jeśli chcesz uzyskać dane odpowiedzi, sprawdź readyState i status po zmianie stanu. xhr.onreadystatechange = function () {if (xhr.readyState == 4 && xhr.status == 200) {console.log (JSON.parse (xhr.responseText)); }}
Stanley Wang

1
@ucheng Jeśli dodamy onreadystatechange z xhr, czy zastąpi on oryginalną metodę zmiany stanu gotowości, lepiej użyj xhrObj.addEventListener ("load", fn)
Mohamed Hussain

128

UWAGA: Zaakceptowana odpowiedź nie daje rzeczywistej odpowiedzi, ponieważ jest wywoływana zbyt wcześnie.

Możesz to zrobić, co generalnie przechwyci wszystkie AJAX na całym świecie i nie zepsuje żadnych wywołań zwrotnych itp., Które mogą zostać przypisane przez biblioteki AJAX stron trzecich.

(function() {
    var origOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function() {
        console.log('request started!');
        this.addEventListener('load', function() {
            console.log('request completed!');
            console.log(this.readyState); //will always be 4 (ajax is completed successfully)
            console.log(this.responseText); //whatever the response was
        });
        origOpen.apply(this, arguments);
    };
})();

Więcej dokumentów na temat tego, co możesz tutaj zrobić za pomocą interfejsu API addEventListener:

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Monitoring_progress

(Uwaga: to nie działa <= IE8)


Dzięki! To działa doskonale. Trochę tylko modyfikuję: zwracam this.statusstatus serwera w tym przypadku, 200jeśli żądanie jest OK, 404 jeśli element nie został znaleziony, 500 itd. Ale kod działa idealnie.
MrMins

1
To może wydawać się stare, ale zasługujesz na całą miłość do tego. Utknąłem głęboko. Wielkie dzięki.
Carles Alcolea

Po prostu dlatego, że trochę tego szukałem. "load"Zdarzenie jest wywoływane tylko w przypadku sukcesu. Jeśli nie zależy Ci na wyniku (tylko że kwerenda się zakończyła) możesz skorzystać z "loadend"wydarzenia
Romuald Brunet

Znalezienie tego zajęło wiele dni. Dziękuję za ten kawałek geniuszu.
David

Wypróbowałem wiele odpowiedzi i działa to doskonale z WKWebView. Miałem problem z używaniem zdarzeń Ajax, ponieważ jQuery może nie zostać załadowany przed wywołaniem assessmentJavascript lub użycia addUserScript. Dziękuję Ci!
Jake Cheng

21

Skoro wspomniałeś jQuery, wiem jQuery oferuje .ajaxSetup()sposób, który wyznacza globalne opcje AJAX, które zawierają wyzwalacze zdarzeń takich jak success, errori beforeSend- co jest, co brzmi jak co szukasz.

$.ajaxSetup({
    beforeSend: function() {
        //do stuff before request fires
    }
});

oczywiście będziesz musiał zweryfikować dostępność jQuery na każdej stronie, na której próbujesz użyć tego rozwiązania.


1
Dzięki za sugestię, ale niestety nie przechwytuje to wywołań AJAX, które są wykonywane bez użycia AJAX.
Yuliy

8
W dzisiejszych wiadomościach: Jednorożce zabite przez połączenia AJAX, które są wykonywane bez użycia AJAX
Dashu

3
nie ma na myśli obiektu JQuery AJAX. Ale nawet wtedy będziesz używać tej samej instancji jquery za każdym razem
Shishir Arora

1
Niezwykle pomocne. Chciałem dodać, kolejnym wyzwalaczem jest kod statusu. Podłączając coś takiego jak statusCode: {403: function () {error msg}, możesz zapewnić globalne sprawdzenie autoryzacji / uprawnień / roli z powrotem do wszystkich funkcji ajax bez konieczności ponownego pisania pojedynczego żądania .ajax.
Watercayman

8

Jest na to pewien podstęp.

Przed uruchomieniem wszystkich skryptów weź oryginalny obiekt XHMHttpReuqest i zapisz go w innym var. Następnie nadpisz oryginalny XMLHttpRequest i przekieruj wszystkie wywołania do niego za pośrednictwem własnego obiektu.

Kod pseudo:

 var savd = XMLHttpRequest;
 XMLHttpRequest.prototype = function() {
         this.init = function() {
         }; // your code
         etc' etc'
 };

10
Ta odpowiedź nie jest do końca poprawna, jeśli zmienisz prototyp obiektu, nawet zapisany zostanie zmieniony. Również cały prototyp jest zastępowany jedną funkcją, która będzie łamać wszystkie żądania AJAX. Zainspirowało mnie to jednak do udzielenia odpowiedzi.
meouw

8

Znalazłem dobrą bibliotekę na Githubie, która dobrze wykonuje swoją pracę, musisz ją dołączyć przed innymi plikami js

https://github.com/jpillora/xhook

oto przykład, który dodaje nagłówek http do każdej przychodzącej odpowiedzi

xhook.after(function(request, response) {
  response.headers['Foo'] = 'Bar';
});

2
Świetny! Ale nie działał z aplikacją Cordova nawet z najnowszą wtyczką przeglądarki Chrome dla przejść dla pieszych!
Islam Attrash

1
To działało idealnie do moich indywidualnych potrzeb: na Wordpress, filtrowanie zdarzeń z imprez Organizatora wtyczki pełnego kalendarza
Alfredo Yong

Nie działa dla wszystkich żądań, niektóre pobieranie i xhr ok, ale na przykład nie łapie xhr z google recaptcha
Sergio Quintero

@SergioQuintero: Zakładam, że to Twój problem z GitHub: github.com/jpillora/xhook/issues/102 . Rozważ usunięcie tutaj swojego komentarza, ponieważ nie był to problem z biblioteką.
thirtydot

@SergioQuintero Wszelkie zastąpienia interfejsów XHR mają miejsce tylko w tym dokumencie, a nie globalnie w przeglądarce, ponieważ byłoby to ogromna luka w bezpieczeństwie / prywatności. reCAPTCHA działa w elemencie iframe i nie możesz tam uruchomić nadpisania.
Walf

5

Korzystając z odpowiedzi „meouw” proponuję skorzystać z następującego rozwiązania, jeśli chcesz zobaczyć wyniki zapytania

function addXMLRequestCallback(callback) {
    var oldSend, i;
    if( XMLHttpRequest.callbacks ) {
        // we've already overridden send() so just add the callback
        XMLHttpRequest.callbacks.push( callback );
    } else {
        // create a callback queue
        XMLHttpRequest.callbacks = [callback];
        // store the native send()
        oldSend = XMLHttpRequest.prototype.send;
        // override the native send()
        XMLHttpRequest.prototype.send = function() {
            // call the native send()
            oldSend.apply(this, arguments);

            this.onreadystatechange = function ( progress ) {
               for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) {
                    XMLHttpRequest.callbacks[i]( progress );
                }
            };       
        }
    }
}

addXMLRequestCallback( function( progress ) {
    if (typeof progress.srcElement.responseText != 'undefined' &&                        progress.srcElement.responseText != '') {
        console.log( progress.srcElement.responseText.length );
    }
});

3

jquery ...

<script>
   $(document).ajaxSuccess(
        function(event, xhr, settings){ 
          alert(xhr.responseText);
        }
   );
</script>

1
jQuery nie będzie przechwytywać żądań wysłanych przy użyciu innych bibliotek. Na przykład ExtJS. Jeśli używasz tylko jQuery, to dobra odpowiedź. W przeciwnym razie nie będzie działać przez cały czas.
npiani

1
nie przechwytuje nawet żądań jquery, jeśli instancja jquery jest inna. W razie potrzeby zmiana w protokole
Shishir Arora

3

Oprócz odpowiedzi meouw musiałem wstrzyknąć kod do elementu iframe, który przechwytuje wywołania XHR i użyłem powyższej odpowiedzi. Jednak musiałem się zmienić

XMLHttpRequest.prototype.send = function(){

Do:

XMLHttpRequest.prototype.send = function(body)

I musiałem się zmienić

oldSend.apply(this, arguments);

Do:

oldSend.call(this, body);

Było to konieczne, aby działał w IE9 z trybem dokumentów IE8 . Jeśli ta modyfikacja nie została dokonana, niektóre wywołania zwrotne generowane przez strukturę komponentów (Visual WebGUI) nie działały. Więcej informacji pod tymi linkami:

Bez tych modyfikacji ogłaszanie zwrotne AJAX nie zakończyło się.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.