summaryrefslogtreecommitdiff
path: root/test/parallel/test-http-pipeline-flood.js
blob: 29df81e857db609fb7d692ab098d391c9892964a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
'use strict';
const common = require('../common');

// Here we are testing the HTTP server module's flood prevention mechanism.
// When writeable.write returns false (ie the underlying send() indicated the
// native buffer is full), the HTTP server cork()s the readable part of the
// stream. This means that new requests will not be read (however request which
// have already been read, but are awaiting processing will still be
// processed).

// Normally when the writable stream emits a 'drain' event, the server then
// uncorks the readable stream, although we aren't testing that part here.

// The issue being tested exists in Node.js 0.10.20 and is resolved in 0.10.21
// and newer.

switch (process.argv[2]) {
  case undefined:
    return parent();
  case 'child':
    return child();
  default:
    throw new Error(`Unexpected value: ${process.argv[2]}`);
}

function parent() {
  const http = require('http');
  const bigResponse = Buffer.alloc(10240, 'x');
  let backloggedReqs = 0;

  const server = http.createServer(function(req, res) {
    res.setHeader('content-length', bigResponse.length);
    if (!res.write(bigResponse)) {
      if (backloggedReqs === 0) {
        // Once the native buffer fills (ie write() returns false), the flood
        // prevention should kick in.
        // This means the stream should emit no more 'data' events. However we
        // may still be asked to process more requests if they were read before
        // the flood-prevention mechanism activated.
        setImmediate(() => {
          req.socket.on('data', common.mustNotCall('Unexpected data received'));
        });
      }
      backloggedReqs++;
    }
    res.end();
  });

  server.on('connection', common.mustCall());

  server.listen(0, function() {
    const spawn = require('child_process').spawn;
    const args = [__filename, 'child', this.address().port];
    const child = spawn(process.execPath, args, { stdio: 'inherit' });
    child.on('close', common.mustCall(function() {
      server.close();
    }));

    server.setTimeout(200, common.mustCallAtLeast(function() {
      child.kill();
    }, 1));
  });
}

function child() {
  const net = require('net');

  const port = +process.argv[3];
  const conn = net.connect({ port });

  let req = `GET / HTTP/1.1\r\nHost: localhost:${port}\r\nAccept: */*\r\n\r\n`;

  req = req.repeat(10240);

  conn.on('connect', write);

  // `drain` should fire once and only once
  conn.on('drain', common.mustCall(write));

  function write() {
    while (false !== conn.write(req, 'ascii'));
  }
}