source

XMLHttpRequest를 중단하는 내부(클라이언트 및 서버)

ittop 2023. 10. 29. 19:58
반응형

XMLHttpRequest를 중단하는 내부(클라이언트 및 서버)

그래서 저는 비동기 자바스크립트 요청을 중단할 때 발생하는 실제 기저 동작이 궁금합니다.이 질문에 관련된 내용이 있었지만 아직 포괄적인 내용을 찾지 못했습니다.

제 가정은 항상 요청을 중단하면 브라우저가 연결을 닫고 처리를 완전히 중지하므로, 서버가 그렇게 설정되어 있으면 서버가 동일하게 처리하게 된다는 것입니다.하지만 여기에는 브라우저 특유의 별난 점이나 엣지 케이스가 있을 수 있다고 생각합니다.

제가 이해하는 바는 다음과 같습니다. 필요하다면 누군가가 수정할 수 있고 앞으로 다른 사람들에게 좋은 참고가 될 수 있기를 바랍니다.

  • XHR 요청 클라이언트 측을 중단하면 브라우저가 내부적으로 소켓을 닫고 처리를 중지합니다.저는 단순히 들어오는 데이터를 무시하고 메모리를 낭비하는 것보다는 이러한 행동을 기대합니다.하지만 저는 IE에 돈을 걸지 않을 겁니다.
  • 서버에서 중단된 요청은 해당 서버에서 실행 중인 작업을 수행합니다.
    • PHP의 기본 동작은 클라이언트 소켓이 닫혔을 때 처리를 중지하는 것으로 알고 있습니다.ignore_user_abort()호출되었습니다.따라서 XHR 연결을 닫으면 서버 전력도 절약됩니다.
    • node.js에서 이것이 어떻게 처리될 수 있는지 정말 궁금합니다. 거기서 약간의 수동 작업이 필요할 것 같습니다.
    • 다른 서버 언어/프레임워크와 그들이 어떻게 작동하는지에 대해서는 전혀 모르지만, 만약 누군가가 구체적으로 기여하고 싶다면 기꺼이 여기에 그것들을 추가하겠습니다.

고객 입장에서는 가장 보기 좋은 곳이 소스에 있으니 이렇게 해요! :)

XMLHtpRequest 의 구현에 abort메서드(XMLHtpRequest.cpp의 줄 1083-1119):

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

그래서 이것으로 볼 때, 대부분의 그룬트 작업은 그 안에서 이루어 진 것으로 보입니다.internalAbort, 다음과 같이 보입니다.

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

저는 C++ 전문가는 아니지만 보기엔internalAbort몇 가지 작업을 수행합니다.

  • 지정된 수신 응답에 대해 현재 수행 중인 모든 처리를 중지합니다.
  • 요청/응답과 관련된 모든 내부 XHR 상태를 지웁니다.
  • 검사관에게 XHR이 실패했음을 보고하라고 말합니다. (정말 흥미롭군요!좋은 콘솔 메시지가 바로 여기서 비롯되었을 겁니다.)
  • 응답 스트림의 "레거시" 버전 또는 응답 스트림의 최신 버전을 닫습니다(이 부분이 질문과 관련된 가장 흥미로운 부분일 수 있습니다).
  • 오류가 제대로 전파되는지 확인하기 위해 몇 가지 스레딩 문제를 다룹니다(댓글 감사합니다).

한참을 헤집다가 HttpResponseBodyDrainer(110-124번 행)에서 재미있는 기능을 발견했습니다.Finish제가 보기엔 요청이 취소되면 결국 호출되는 것처럼 보입니다.

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, BasicHttpStream에서는 HttpStreamParser로 위임합니다.::Close, A가 주어졌을 때non-reusableflag(에 표시된 것처럼 요청이 중단될 때 발생하는 것으로 보임)HttpResponseDrainer), 소켓을 닫습니다.

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

따라서 클라이언트에서 발생하는 일에 관해서는, 적어도 Chrome의 경우에는 초기 직관이 정확했던 것 같습니다. :) 대부분의 별난 점과 엣지 케이스는 브라우저별 처리뿐만 아니라 스케줄링/이벤트 알림/스레딩 문제와 관련이 있는 것 같습니다.중단된 XHR을 devtools 콘솔에 보고합니다.

서버 측면에서는 노드의 경우JS http response 개체에서 'close' 이벤트를 듣고 싶습니다.간단한 예는 다음과 같습니다.

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

완료하기 전에 이를 실행하고 요청을 취소해 보십시오.콘솔에 오류가 나타납니다.

이것이 유용한 것을 발견하셨길 바랍니다.크롬/블링크 소스를 파헤치는 것은 정말 재미있었습니다 :)

언급URL : https://stackoverflow.com/questions/24258279/internals-client-and-server-of-aborting-an-xmlhttprequest

반응형