• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2// Flags: --expose-internals
3const common = require('../common');
4const assert = require('assert');
5const { async_id_symbol } = require('internal/async_hooks').symbols;
6const http = require('http');
7
8// Regression test for https://github.com/nodejs/node/issues/13325
9// Checks that an http.Agent properly asyncReset()s a reused socket handle, and
10// re-assigns the fresh async id to the reused `net.Socket` instance.
11
12// Make sure a single socket is transparently reused for 2 requests.
13const agent = new http.Agent({
14  keepAlive: true,
15  keepAliveMsecs: Infinity,
16  maxSockets: 1
17});
18
19const server = http.createServer(common.mustCall((req, res) => {
20  req.once('data', common.mustCallAtLeast(() => {
21    res.writeHead(200, { 'Content-Type': 'text/plain' });
22    res.write('foo');
23  }));
24  req.on('end', common.mustCall(() => {
25    res.end('bar');
26  }));
27}, 2)).listen(0, common.mustCall(() => {
28  const port = server.address().port;
29  const payload = 'hello world';
30
31  // First request. This is useless except for adding a socket to the
32  // agent’s pool for reuse.
33  const r1 = http.request({
34    agent, port, method: 'POST'
35  }, common.mustCall((res) => {
36    // Remember which socket we used.
37    const socket = res.socket;
38    const asyncIdAtFirstRequest = socket[async_id_symbol];
39    assert.ok(asyncIdAtFirstRequest > 0, `${asyncIdAtFirstRequest} > 0`);
40    // Check that request and response share their socket.
41    assert.strictEqual(r1.socket, socket);
42
43    res.on('data', common.mustCallAtLeast(() => {}));
44    res.on('end', common.mustCall(() => {
45      // setImmediate() to give the agent time to register the freed socket.
46      setImmediate(common.mustCall(() => {
47        // The socket is free for reuse now.
48        assert.strictEqual(socket[async_id_symbol], -1);
49
50        // Second request. To re-create the exact conditions from the
51        // referenced issue, we use a POST request without chunked encoding
52        // (hence the Content-Length header) and call .end() after the
53        // response header has already been received.
54        const r2 = http.request({
55          agent, port, method: 'POST', headers: {
56            'Content-Length': payload.length
57          }
58        }, common.mustCall((res) => {
59          const asyncId = res.socket[async_id_symbol];
60          assert.ok(asyncId > 0, `${asyncId} > 0`);
61          assert.strictEqual(r2.socket, socket);
62          // Empty payload, to hit the “right” code path.
63          r2.end('');
64
65          res.on('data', common.mustCallAtLeast(() => {}));
66          res.on('end', common.mustCall(() => {
67            // Clean up to let the event loop stop.
68            server.close();
69            agent.destroy();
70          }));
71        }));
72
73        // Schedule a payload to be written immediately, but do not end the
74        // request just yet.
75        r2.write(payload);
76      }));
77    }));
78  }));
79  r1.end(payload);
80}));
81