nodejs / nodejs/node

No keepAliveTimeout for HTTP server after answering a POST request synchronously

オープン
#39,137 コメント 9 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

help wanted http
主要言語
JavaScript
スター
122k
フォーク
37.3k
平均マージ
4日 2時間
マージ済み PR(30日)
283

説明

  • Version: v16.4.0
  • Platform: Linux test01 4.19.0-13-amd64 # 1 SMP Debian 4.19.160-2 (2020-11-28) x86_64 GNU/Linux
  • Subsystem: http
What steps will reproduce the bug?

When using the "normal" NodeJS "hello world" HTTP server example, which answers any request synchronously, and sending it a POST request with a body, server.keepAliveTimeout (default 5 sec) does not work. The HTTP connection remains open ("forever"?).

Complete Testcase

Here we test all 4 combinations of sync/async response and get/post request to a simple HTTP server, and expect a connection close from server after 1 sec - which works fine for all combinations except "synchronous response for a POST request". If the connection is still open, we try another request - to prove, the connection still works:

'use strict';
const http = require('http');
const net = require('net');

const server = http.createServer((req, res) => {
  if (req.url === '/async') {
    req.on('data', () => {});
    req.on('end', () => {
      res.end('test-body');
      console.log('Server: Sent response asynchronously');
    });
  } else {
    res.end('test-body');
    console.log('Server: Sent response synchronously');
  }
});

server.keepAliveTimeout = 1000;

server.listen(0, async () => {
  await sendRequests('async', 'get');
  await sendRequests('async', 'post');
  await sendRequests('sync', 'get');
  await sendRequests('sync', 'post'); // keepAliveTimeout does not work here!!

  server.close();
});

async function sendRequests(type, method) {
  console.log(`=== Testing ${type} response for method "${method}" and wait for keep-alive-close`);

  return new Promise((resolve) => {
    const client = new net.Socket();
    let nextReqTimeout = null;

    client.connect(server.address().port, '127.0.0.1', () => {
      console.log('Client: Connected to server');

      httpRequest();

      nextReqTimeout = setTimeout(() => {
        console.log('Client: ERROR: HTTP-Connection is still open - trying another request');
        httpRequest();

        nextReqTimeout = setTimeout(() => {
          console.log('Client: ERROR: HTTP-Connection is still open - closing from client side now');
          nextReqTimeout = null;
          client.end();
        }, server.keepAliveTimeout * 2);
      }, server.keepAliveTimeout * 2);
    });

    client.on('data', function(data) {
      console.log('Client: Got response data');
    });

    client.on('close', function() {
      if (nextReqTimeout) {
        console.log('Client: Server closed connection as expected');
        clearTimeout(nextReqTimeout);
      }
      resolve();
    });

    function httpRequest() {
      const rawRequests = {
        'post': 'POST /' + type + ' HTTP/1.1\r\n' +
          'Connection: keep-alive\r\n' +
          'Content-Type: application/x-www-form-urlencoded\r\n' +
          'Content-Length: 10\r\n' +
          '\r\n' +
          'Test=67890',
        'get': 'GET /' + type + ' HTTP/1.1\r\n' +
          'Connection: keep-alive\r\n' +
          '\r\n'
      };

      console.log('Client: Sending request');
      client.write(rawRequests[method]);
    }
  });
}
What is the expected behavior?

The keepAliveTimeout should also work after answering a POST request synchronously.

Possible cause

Maybe this is a race condition here:

  • In resOnFinish the server.keepAliveTimeout is set on the socket, after sending the response
  • After that, socketOnData is called internally for the remaining POST body, which calls onParserExecuteCommon, which resets the socket timeout first
  • Normally socketOnData is called before sending the response, so in most cases it works as expected

Maybe there should be a swicth, to not reset the socket-timeout when socketOnData is called after the response was sent?

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

まず提供された JavaScript の再現コードを実行し、次に lib/_http_server.js の resOnFinish、socketOnData、onParserExecuteCommon 周辺を読みます。同期レスポンスの後に残りの POST ボディがソケットのタイムアウトをリセットする仕組みを追跡します。他の 3 つのケースと同様に、同期 POST ケースが server.keepAliveTimeout の後に終了し、報告された動作に対するリグレッションカバレッジが追加されていれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
javascript
領域
api, backend
issue の種類
バグ
難易度
3/5
見積もり時間
1〜2日
活発さ
活発
明瞭さ
明確に書かれている
初心者へのやさしさ
68/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。