Internals (cliente y server) de abortar un XMLHttpRequest

Por lo tanto, tengo curiosidad acerca de los comportamientos subyacentes reales que se producen al cancelar una request de JavaScript asíncrona. Hubo información relacionada en esta pregunta, pero aún no he encontrado nada exhaustivo.

Mi suposition siempre ha sido que cancelar la request hace que el browser cierre la connection y deje de procesarla por completo, haciendo que el server haga lo mismo si se ha configurado para hacerlo. Sin embargo, imagino que podría haber caprichos específicos del browser o casos extremos en los que no estoy pensando.

Mi comprensión es la siguiente, espero que alguien pueda corregirla si es necesario y que esta pueda ser una buena reference para que otros sigan adelante.

  • Anular el pedido del cliente XHR hace que el browser cierre internamente el socket y deje de procesarlo. Esperaría este comportamiento en lugar de simplemente ignorar los datos que llegan y el desperdicio de memory. Sin embargo, no estoy apostando por IE.
  • Una request cancelada en el server correspondería a lo que se está ejecutando allí:
    • Sé con PHP que el comportamiento pnetworkingeterminado es detener el procesamiento cuando el socket del cliente está cerrado, a less que se haya llamado a ignore_user_abort() . Por lo tanto, el cierre de las conexiones XHR le ahorra energía al server.
    • Estoy realmente interesado en saber cómo se podría manejar esto en node.js, supongo que algún trabajo manual sería necesario allí.
    • No tengo idea acerca de otros lenguajes / frameworks de serveres y cómo se comportan, pero si alguien quiere contribuir con detalles, me complace agregarlos aquí.

One Solution collect form web for “Internals (cliente y server) de abortar un XMLHttpRequest”

Para el cliente, el mejor lugar para search está en la fuente, ¡así que hagámoslo! 🙂

Veamos la implementación de Blink del método de abort de XMLHttpRequest (líneas 1083-1119 en XMLHttpRequest.cpp ):

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

Por lo tanto, a partir de esto, parece que la mayoría del trabajo se hace dentro de internalAbort , que se ve así:

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

No soy un experto en C ++ pero, por lo que se ve, el internalAbort hace algunas cosas:

  • Detiene cualquier procesamiento que está haciendo actualmente en una respuesta entrante dada
  • Borra cualquier estado XHR interno asociado con la request / respuesta
  • Le dice al inspector que informe que el XHR falló (¡esto es realmente interesante! Apuesto a que es de donde se originan esos agradables posts de console)
  • Cierra la versión "henetworkingada" de una secuencia de respuesta o la versión moderna de la secuencia de respuesta (esta es probablemente la parte más interesante relacionada con su pregunta)
  • Se ocupa de algunos problemas de subprocesamiento para garantizar que el error se propague correctamente (gracias, comentarios).

Después de investigar mucho, encontré una function interesante dentro de HttpResponseBodyDrainer (líneas 110-124) llamada Finish que a mí me parece algo que eventualmente se llamaría cuando se cancela una request:

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

Resulta que stream_->Close , al less en BasicHttpStream, delega en HttpStreamParser ::Close , que, cuando se le da un indicador non-reusable (que parece suceder cuando se cancela la request, como se ve en HttpResponseDrainer ), cierra el socket

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

Entonces , en términos de lo que sucede en el cliente, al less en el caso de Chrome, parece que tus intuiciones iniciales fueron correctas por lo que yo sé 🙂 parece que la mayoría de las peculiaridades y casos extremos tienen que ver con la progtwigción / problemas de notificación / enhebrado de events, así como manejo específico del browser, por ejemplo, informar el XHR cancelado a la console de devtools.

En términos del server, en el caso de NodeJS, querría escuchar el evento 'cerrar' en el object de respuesta http. Aquí hay un ejemplo simple:

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

Intenta ejecutar eso y cancelar la request antes de que se complete. Verás un error en tu console.

Espero que hayas encontrado esto útil. Excavar a través de la fuente Chromium / Blink fue muy divertido 🙂

  • El evento de progreso de XMLHttpRequest avanza mucho más rápido que la carga real
  • Acceder a los datos enviados por request xhr en node.js
  • Javascript no está cargando datos binarys
  • Javascript XHR envía multipart / form-data
  • jQuery AJAX activa la callback de error en la descarga de la window: ¿cómo puedo filtrar la descarga y solo detectar errores reales?
  • La request de XHR muestra el estado cancelado en Chrome (no en CORS)
  • El método Http cambia de POST a OPCIONES al cambiar el tipo de contenido
  • arrojando y atrapando la exception de la function
  • javascript: cómo recuperar el contenido de una página web
  • node.js - XMLHttpRequest, get información del encabezado
  • Problemas con la API de historial de Chrome
  • Javascript tiene muchos buenos JS marco (como Node.js AngularJS Vue.js React.js) es el mejor lenguaje de script.