2014-06-17 31 views
6

Quindi sono curioso di sapere i comportamenti effettivi di fondo che si verificano durante l'interruzione di un asincrona javascript richiesta. C'erano alcune informazioni correlate in this question ma non ho ancora trovato nulla di completo.Internals (client e server) di abortire un XMLHttpRequest

La mia ipotesi è sempre stata che l'interruzione della richiesta fa sì che il browser chiuda la connessione e smetta di elaborarlo interamente, facendo sì che il server faccia lo stesso se è stato configurato per farlo. Immagino comunque che ci possano essere stranezze o casi limite specifici del browser, a cui non sto pensando.

La mia comprensione è il seguente, Sto sperando che qualcuno può correggere, se necessario, e che questo può essere un buon riferimento per altri andando avanti.

  • L'interruzione della richiesta XHR lato client causa la chiusura interna del socket da parte del browser e l'interruzione dell'elaborazione. Mi aspetterei questo comportamento piuttosto che semplicemente ignorando i dati in arrivo e sprecando memoria. Però non sto scommettendo su IE.
  • Una richiesta abortita sul server sarebbe fino a quello che è in esecuzione c'è:
    • so con PHP il comportamento predefinito è quello di interrompere l'elaborazione quando il socket client è chiuso, a meno che non ignore_user_abort() è stato chiamato. Quindi chiudere le connessioni XHR ti risparmia anche il server.
    • Sono veramente curioso di sapere come questo potrebbe essere gestita in node.js, presumo qualche lavoro manuale sarebbe necessaria lì.
    • ho idea davvero di altri server di lingue/strutture e come si comportano, ma se qualcuno vuole contribuire specifiche sono felice di aggiungere qui.
+0

Hm, [la specifica] (http://www.w3.org/TR/XMLHttpRequest/#the-abort() -method) è anormalmente vaga che cosa "cancellare istanze' fetch' "significa – Bergi

+0

Sì, le migliori informazioni in esso contenute sono" * Annulla qualsiasi istanza dell'algoritmo di recupero aperto da questo oggetto * "sotto la definizione di cosa significa terminare la richiesta. Sortof implica che la presa dovrebbe essere chiusa ma non è sicuramente chiara .. – pospi

risposta

10

Per il client, il posto migliore per cercare è nella fonte, quindi cerchiamo di fare questo! :)

Diamo un'occhiata alla implementazione di Blink di abort metodo (linee 1083-1119 in XMLHttpRequest.cpp) di XMLHttpRequest:

void XMLHttpRequest::abort() 
{ 
    WTF_LOG(Network, "XMLHttpRequest %p abort()", this); 
    // internalAbort() clears |m_loader|. Compute |sendFlag| now. 
    // 
    // |sendFlag| corresponds to "the send() flag" defined in the XHR spec. 
    // 
    // |sendFlag| is only set when we have an active, asynchronous loader. 
    // Don't use it as "the send() flag" when the XHR is in sync mode. 
    bool sendFlag = m_loader; 
    // internalAbort() clears the response. Save the data needed for 
    // dispatching ProgressEvents. 
    long long expectedLength = m_response.expectedContentLength(); 
    long long receivedLength = m_receivedLength; 
    if (!internalAbort()) 
     return; 
    // The script never gets any chance to call abort() on a sync XHR between 
    // send() call and transition to the DONE state. It's because a sync XHR 
    // doesn't dispatch any event between them. So, if |m_async| is false, we 
    // can skip the "request error steps" (defined in the XHR spec) without any 
    // state check. 
    // 
    // FIXME: It's possible open() is invoked in internalAbort() and |m_async| 
    // becomes true by that. We should implement more reliable treatment for 
    // nested method invocations at some point. 
    if (m_async) { 
     if ((m_state == OPENED && sendFlag) || m_state == HEADERS_RECEIVED || m_state == LOADING) { 
      ASSERT(!m_loader); 
      handleRequestError(0, EventTypeNames::abort, receivedLength, expectedLength); 
     } 
    } 
    m_state = UNSENT; 
} 

Quindi da questo, sembra che la maggior parte del lavoro sporco avviene entro internalAbort, che assomiglia a questo:

bool XMLHttpRequest::internalAbort() 
{ 
    m_error = true; 
    if (m_responseDocumentParser && !m_responseDocumentParser->isStopped()) 
     m_responseDocumentParser->stopParsing(); 
    clearVariablesForLoading(); 
    InspectorInstrumentation::didFailXHRLoading(executionContext(), this, this); 
    if (m_responseLegacyStream && m_state != DONE) 
     m_responseLegacyStream->abort(); 
    if (m_responseStream) { 
     // When the stream is already closed (including canceled from the 
     // user), |error| does nothing. 
     // FIXME: Create a more specific error. 
     m_responseStream->error(DOMException::create(!m_async && m_exceptionCode ? m_exceptionCode : AbortError, "XMLHttpRequest::abort")); 
    } 
    clearResponse(); 
    clearRequest(); 
    if (!m_loader) 
     return true; 
    // Cancelling the ThreadableLoader m_loader may result in calling 
    // window.onload synchronously. If such an onload handler contains open() 
    // call on the same XMLHttpRequest object, reentry happens. 
    // 
    // If, window.onload contains open() and send(), m_loader will be set to 
    // non 0 value. So, we cannot continue the outer open(). In such case, 
    // just abort the outer open() by returning false. 
    RefPtr<ThreadableLoader> loader = m_loader.release(); 
    loader->cancel(); 
    // If abort() called internalAbort() and a nested open() ended up 
    // clearing the error flag, but didn't send(), make sure the error 
    // flag is still set. 
    bool newLoadStarted = m_loader; 
    if (!newLoadStarted) 
     m_error = true; 
    return !newLoadStarted; 
} 

non sono un esperto di C++ ma dagli sguardi di esso, internalAbort fa un paio di cose:

  • Interrompe qualsiasi elaborazione sta facendo attualmente su una data risposta in arrivo
  • cancella qualsiasi stato XHR interno associato alla richiesta/risposta
  • Indica l'ispettore di riferire che la XHR fallito (questo è davvero interessante! Scommetto che è da dove provengono quei bei messaggi della console)
  • Chiude la versione "legacy" di un flusso di risposta o la versione moderna del flusso di risposta (questa è probabilmente la parte più interessante pertinente alla tua domanda)
  • Offerte con alcuni problemi di threading per garantire che l'errore venga propagato correttamente (grazie, commenti).

Dopo aver fatto un sacco di scavare intorno, mi sono imbattuto in un interessante funzione all'interno HttpResponseBodyDrainer (linee 110-124) chiamato Finish che per me assomiglia a qualcosa che sarebbe poi stata chiamata quando una richiesta viene annullata:

void HttpResponseBodyDrainer::Finish(int result) { 
    DCHECK_NE(ERR_IO_PENDING, result); 
    if (session_) 
    session_->RemoveResponseDrainer(this); 
    if (result < 0) { 
    stream_->Close(true /* no keep-alive */); 
    } else { 
    DCHECK_EQ(OK, result); 
    stream_->Close(false /* keep-alive */); 
    } 
    delete this; 
} 

risulta che stream_->Close, almeno nel BasicHttpStream, delegati al HttpStreamParser::Close, che, quando somministrato un flag non-reusable (che sembra accadere quando la richiesta viene interrotto, come visto in HttpResponseDrainer), si chiude la presa:

void HttpStreamParser::Close(bool not_reusable) { 
    if (not_reusable && connection_->socket()) 
    connection_->socket()->Disconnect(); 
    connection_->Reset(); 
} 

Così, in termini di ciò che accade sul client, almeno nel caso di Chrome, sembra che il tuo intuizioni iniziali erano corrette, per quanto posso dire :) sembra che la maggior parte delle stranezze e casi limite hanno a che fare con problemi di programmazione/notifica di eventi/threading, così come la gestione specifica del browser, ad es riportando l'XHR abortito alla console devtools.

In termini di server, nel caso di NodeJS si desidera ascoltare lo 'close' event sull'oggetto di risposta http. Ecco un semplice esempio:

'use strict'; 

var http = require('http'); 

var server = http.createServer(function(req, res) { 
    res.on('close', console.error.bind(console, 'Connection terminated before response could be sent!')); 
    setTimeout(res.end.bind(res, 'yo'), 2000); 
}); 

server.listen(8080); 

Prova a eseguirlo e annulla la richiesta prima che venga completata. Vedrai un errore nella tua console.

Spero che tu abbia trovato utile questo. Scavare attraverso la sorgente Chromium/Blink è stato molto divertente :)

+0

Eeeek !!! hai fatto sicuramente un tuffo in profondità. Grazie mille per aver dato un'occhiata: D Non ho ancora testato il codice del nodo (non ricordo nemmeno quale progetto stavo giocando originariamente quando l'ho chiesto), ma immagino YMMV a seconda del browser utilizzato comunque. – pospi

+0

Per immergersi ancora più in profondità, ecco due problemi tecnici molto recenti relativi a questo: https://github.com/whatwg/xhr/issues/88 e https://github.com/whatwg/xhr/issues/94 – bernie