中止XMLHttpRequest的内部(客户端和服务器)

时间:2014-06-17 07:40:44

标签: javascript ajax xmlhttprequest internals

所以我很好奇在中止异步javascript请求时发生的实际底层行为。 <{3}}中有一些相关信息,但我还没有找到任何全面的信息。

我的假设一直是中止请求会导致浏览器关闭连接并完全停止处理,从而导致服务器在设置完成后也这样做。但我想,在这里我可能没有考虑特定于浏览器的怪癖或边缘情况。

我的理解如下,我希望有人可以在必要时纠正它,这对其他人来说是一个很好的参考。

  • 中止XHR请求客户端会导致浏览器在内部关闭套接字并停止处理它。我希望这种行为而不是简单地忽略进来的数据并浪费内存。我不打算在IE上打赌。
  • 服务器上的中止请求取决于在那里运行的内容:
    • 我知道PHP的默认行为是在客户端套接字关闭时停止处理,除非调用ignore_user_abort()。因此,关闭XHR连接也可以节省服务器电源。
    • 我真的很想知道如何在node.js中处理这个问题,我假设在那里需要一些手工工作。
    • 我根本不知道其他服务器语言/框架以及它们的行为方式,但如果有人想提供具体内容,我很乐意在这里添加它们。

1 个答案:

答案 0 :(得分:18)

对于客户来说,最好看的地方是在源头,所以让我们这样做! :)

让我们看看Blink的XMLHttpRequest var s = format("$1, $2, $3", ["$2", "two", "three"]); // returns: "two, two, three", // needed: "$2, two, three" 方法的实现(XMLHttpRequest.cpp中的第1083-1119行):

format

因此,看起来大部分咕噜声工作都是在abort内完成的,如下所示:

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

我不是C ++专家,但从它的外观来看,internalAbort做了一些事情:

  • 停止对给定传入响应进行的任何处理
  • 清除与请求/响应相关联的任何内部XHR状态
  • 告诉检查员报告XHR失败了(这真的很有意思!我敢打赌这就是那些好的控制台消息来源)
  • 关闭响应流的“遗留”版本或响应流的现代版本(这可能是与您的问题有关的最有趣的部分)
  • 处理一些线程问题以确保错误传播正确(谢谢,评论)。

在进行了大量的挖掘后,我在HttpResponseBodyDrainer(第110-124行)中遇到了一个名为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; } 的有趣函数,对我来说这看起来像是在请求时最终会调用的函数取消:

internalAbort

事实证明Finish,至少在BasicHttpStream中,委托给HttpStreamParser 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; } ,当给出stream_->Close标志时(似乎确实发生了)当请求被中止时,如::Close中所示),关闭套接字:

non-reusable

所以,就客户端上发生的事情而言,至少在Chrome的情况下,看起来你的初始直觉是正确的,据我所知:)似乎大部分的怪癖和边缘情况与调度/事件通知/线程问题以及特定于浏览器的处理有关,例如将流产的XHR报告给devtools控制台。

就服务器而言,在NodeJS的情况下,您希望在http响应对象上侦听'close' event。这是一个简单的例子:

HttpResponseDrainer

尝试运行该请求并在请求完成之前取消该请求。您将在控制台上看到错误。

希望你发现这很有用。挖掘Chromium / Blink源非常有趣:)