对于node.js HTTP,res.end() 如何保证套接字断开连接?

发布于 2024-12-03 10:58:10 字数 2400 浏览 0 评论 0原文

这是node.js的end实现:

OutgoingMessage.prototype.end = function(data, encoding) {
  if (this.finished) {
    return false;
  }
  if (!this._header) {
    this._implicitHeader();
  }

  if (data && !this._hasBody) {
    console.error('This type of response MUST NOT have a body. ' +
                  'Ignoring data passed to end().');
    data = false;
  }

  var ret;

  var hot = this._headerSent === false &&
            typeof(data) === 'string' &&
            data.length > 0 &&
            this.output.length === 0 &&
            this.connection &&
            this.connection.writable &&
            this.connection._httpMessage === this;

  if (hot) {
    // Hot path. They're doing
    //   res.writeHead();
    //   res.end(blah);
    // HACKY.

    if (this.chunkedEncoding) {
      var l = Buffer.byteLength(data, encoding).toString(16);
      ret = this.connection.write(this._header + l + CRLF +
                                  data + '\r\n0\r\n' +
                                  this._trailer + '\r\n', encoding);
    } else {
      ret = this.connection.write(this._header + data, encoding);
    }
    this._headerSent = true;

  } else if (data) {
    // Normal body write.
    ret = this.write(data, encoding);
  }

  if (!hot) {
    if (this.chunkedEncoding) {
      ret = this._send('0\r\n' + this._trailer + '\r\n'); // Last chunk.
    } else {
      // Force a flush, HACK.
      ret = this._send('');
    }
  }

  this.finished = true;

  // There is the first message on the outgoing queue, and we've sent
  // everything to the socket.
  if (this.output.length === 0 && this.connection._httpMessage === this) {
    debug('outgoing message end.');
    this._finish();
  }

  return ret;
};

来源:https://github.com/joyent/node/blob/master/lib/http.js#L645

显然,只有当 output.length === 0 时,连接才“完成”

因此,如果仍有数据等待写入,并且接收客户端由于某种原因无法接收该数据,那么请求是否会结束?

我还看到过这样的问题:在尝试结束 Flash 上传器发出的 http 请求时,结束无效。我最终做了以下事情,这确实有帮助:

    res.end(failureJSON, 'utf8');
    req.once('end', function _destroyConn() {
        req.connection.destroy();
    });

看起来很黑客。无论如何,是否需要使用 req.connection.destroy 的这种行为来保证与套接字的断开连接?

This is node.js' end implementation:

OutgoingMessage.prototype.end = function(data, encoding) {
  if (this.finished) {
    return false;
  }
  if (!this._header) {
    this._implicitHeader();
  }

  if (data && !this._hasBody) {
    console.error('This type of response MUST NOT have a body. ' +
                  'Ignoring data passed to end().');
    data = false;
  }

  var ret;

  var hot = this._headerSent === false &&
            typeof(data) === 'string' &&
            data.length > 0 &&
            this.output.length === 0 &&
            this.connection &&
            this.connection.writable &&
            this.connection._httpMessage === this;

  if (hot) {
    // Hot path. They're doing
    //   res.writeHead();
    //   res.end(blah);
    // HACKY.

    if (this.chunkedEncoding) {
      var l = Buffer.byteLength(data, encoding).toString(16);
      ret = this.connection.write(this._header + l + CRLF +
                                  data + '\r\n0\r\n' +
                                  this._trailer + '\r\n', encoding);
    } else {
      ret = this.connection.write(this._header + data, encoding);
    }
    this._headerSent = true;

  } else if (data) {
    // Normal body write.
    ret = this.write(data, encoding);
  }

  if (!hot) {
    if (this.chunkedEncoding) {
      ret = this._send('0\r\n' + this._trailer + '\r\n'); // Last chunk.
    } else {
      // Force a flush, HACK.
      ret = this._send('');
    }
  }

  this.finished = true;

  // There is the first message on the outgoing queue, and we've sent
  // everything to the socket.
  if (this.output.length === 0 && this.connection._httpMessage === this) {
    debug('outgoing message end.');
    this._finish();
  }

  return ret;
};

Source: https://github.com/joyent/node/blob/master/lib/http.js#L645

Apparently, the connection is only "finished" when output.length === 0.

So, if there is still data waiting to be written, and the receiving client for some reason is dodgy about receiving this data, will the request ever be ended?

I have also seen such issue where an end is not effective while trying to end a http request made by a flash uploader. I ended up doing the following, which did help:

    res.end(failureJSON, 'utf8');
    req.once('end', function _destroyConn() {
        req.connection.destroy();
    });

Seems very hackish. Anyway, is such behavior with req.connection.destroy needed to guarantee a disconnection from the socket?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

羁拥 2024-12-10 10:58:10

不幸的是,res.end() 并不直接“保证套接字断开连接”,因为它需要考虑 HTTP Keep-Alive。根据文档,end 告诉服务器所有内容都已发送,并且响应已完成。是否立即断开连接完全取决于服务器对象。

为了更具体地回答您的问题,重要的是响应需要发出 finish 事件。如果您看一下 _finish() 的实现,它几乎只是发出事件。

正如您所指出的,它并不总是直接调用 _finish()…但它确实设置了 this.finished = true。当_flush()执行时,它会发送所有剩余的数据,然后调用_finish()

这有点复杂,我认为我不能在没有犯错的风险的情况下详细讨论。

至于连接有时不会关闭,您是否检查过 keep-alive 配置是否正确?如果默认使用 keep-alive 设置 HTTP 连接,则调用 end 将不会关闭套接字。

如果您打印出res.shouldKeepAlive,它会告诉您服务器是否正在尝试使用keep-alive。如果您想阻止服务器执行此操作,请在请求处理程序开始时将其设置为 false

Unfortunately, res.end() does not directly “guarantee a disconnection of the socket” because it needs to account for HTTP Keep-Alive. According to the docs, end tells the server that everything has been sent, and that the response is complete. It’s entirely up to the server object whether or not to disconnect immediately.

To answer your question more specifically, the important thing is that the response needs to emit a finish event. If you take a look at the implementation of _finish(), it pretty much just emits the event.

As you noted though, it doesn’t always call _finish() directly…but it did set this.finished = true. When _flush() executes, it sends any remaining data and THEN calls _finish().

It’s kind of complicated, and I don’t think I can go into any more detail without the risk of being wrong.

As far as connections sometimes not closing, have you checked if you have keep-alive configured properly? If the HTTP connection is set up with keep-alive by default, then calling end will not close the socket.

If your print out res.shouldKeepAlive, it will tell you if your server is trying to use keep-alive. Set it to false at the start of your request handler if you want to stop the server from doing this.

南风起 2024-12-10 10:58:10

我不知道这是否对您有帮助,因为我正在为节点 4.4+ 构建框架,但我已经确认您可以在响应中发送 Connection: close 标头,以让节点关闭连接。

let res = getResponseSomehow()

res.statusCode = 408
res.setHeader("Connection", "close")
res.end()

另外,您的销毁代码可以使用以下调整:

// First we give node the time to close the connection
// We can give it 2 seconds
let socket = getSocketSomehow();
let timer = setTimeout(function() {
  socket.destroy();
}, 2000);

socket.on("close", function() {
  clearTimeout(timer);
});

我不太确定这是否是您想要的 close 事件。我通常尝试使用库并远离 net api,所以这只是一个猜测。

I don't know if this helps you as I am building my framework for node 4.4+ but I have confirmed that you can send Connection: close header in your response to get node to close the connection.

let res = getResponseSomehow()

res.statusCode = 408
res.setHeader("Connection", "close")
res.end()

Also your destroy code could use the following tweak:

// First we give node the time to close the connection
// We can give it 2 seconds
let socket = getSocketSomehow();
let timer = setTimeout(function() {
  socket.destroy();
}, 2000);

socket.on("close", function() {
  clearTimeout(timer);
});

I'm not quite sure if it's the close event you want. I normally try to use a library and stay away from the net api so this is just a guess.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文