1'use strict';
2const common = require('../common');
3const assert = require('assert');
4const http = require('http');
5const net = require('net');
6
7const agent = new http.Agent({
8  keepAlive: true,
9  maxFreeSockets: Infinity,
10  maxSockets: Infinity,
11  maxTotalSockets: Infinity,
12});
13
14const server = net.createServer({
15  pauseOnConnect: true,
16}, (sock) => {
17  // Do not read anything from `sock`
18  sock.pause();
19  sock.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n');
20});
21
22server.listen(0, common.mustCall(() => {
23  sendFstReq(server.address().port);
24}));
25
26function sendFstReq(serverPort) {
27  const req = http.request({
28    agent,
29    host: '127.0.0.1',
30    port: serverPort,
31  }, (res) => {
32    res.on('data', noop);
33    res.on('end', common.mustCall(() => {
34      // Agent's socket reusing code is registered to process.nextTick(),
35      // and will be run after this function, make sure it take effect.
36      setImmediate(sendSecReq, serverPort, req.socket.localPort);
37    }));
38  });
39
40  // Make the `req.socket` non drained, i.e. has some data queued to write to
41  // and accept by the kernel. In Linux and Mac, we only need to call `req.end(aLargeBuffer)`.
42  // However, in Windows, the mechanism of acceptance is loose, the following code is a workaround
43  // for Windows.
44
45  /**
46   * https://docs.microsoft.com/en-US/troubleshoot/windows/win32/data-segment-tcp-winsock says
47   *
48   * Winsock uses the following rules to indicate a send completion to the application
49   * (depending on how the send is invoked, the completion notification could be the
50   * function returning from a blocking call, signaling an event, or calling a notification
51   * function, and so forth):
52   * - If the socket is still within SO_SNDBUF quota, Winsock copies the data from the application
53   * send and indicates the send completion to the application.
54   * - If the socket is beyond SO_SNDBUF quota and there's only one previously buffered send still
55   * in the stack kernel buffer, Winsock copies the data from the application send and indicates
56   * the send completion to the application.
57   * - If the socket is beyond SO_SNDBUF quota and there's more than one previously buffered send
58   * in the stack kernel buffer, Winsock copies the data from the application send. Winsock doesn't
59   * indicate the send completion to the application until the stack completes enough sends to put
60   * back the socket within SO_SNDBUF quota or only one outstanding send condition.
61   */
62
63  req.on('socket', () => {
64    req.socket.on('connect', () => {
65      // Print tcp send buffer information
66      console.log(process.report.getReport().libuv.filter((handle) => handle.type === 'tcp'));
67
68      const dataLargerThanTCPSendBuf = Buffer.alloc(1024 * 1024 * 64, 0);
69
70      req.write(dataLargerThanTCPSendBuf);
71      req.uncork();
72      if (process.platform === 'win32') {
73        assert.ok(req.socket.writableLength === 0);
74      }
75
76      req.write(dataLargerThanTCPSendBuf);
77      req.uncork();
78      if (process.platform === 'win32') {
79        assert.ok(req.socket.writableLength === 0);
80      }
81
82      req.end(dataLargerThanTCPSendBuf);
83      assert.ok(req.socket.writableLength > 0);
84    });
85  });
86}
87
88function sendSecReq(serverPort, fstReqCliPort) {
89  // Make the second request, which should be sent on a new socket
90  // because the first socket is not drained and hence can not be reused
91  const req = http.request({
92    agent,
93    host: '127.0.0.1',
94    port: serverPort,
95  }, (res) => {
96    res.on('data', noop);
97    res.on('end', common.mustCall(() => {
98      setImmediate(sendThrReq, serverPort, req.socket.localPort);
99    }));
100  });
101
102  req.on('socket', common.mustCall((sock) => {
103    assert.notStrictEqual(sock.localPort, fstReqCliPort);
104  }));
105  req.end();
106}
107
108function sendThrReq(serverPort, secReqCliPort) {
109  // Make the third request, the agent should reuse the second socket we just made
110  const req = http.request({
111    agent,
112    host: '127.0.0.1',
113    port: serverPort,
114  }, noop);
115
116  req.on('socket', common.mustCall((sock) => {
117    assert.strictEqual(sock.localPort, secReqCliPort);
118    process.exit(0);
119  }));
120}
121
122function noop() { }
123