1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23const common = require('../common');
24const assert = require('assert');
25const http = require('http');
26const Agent = require('_http_agent').Agent;
27
28let name;
29
30const agent = new Agent({
31  keepAlive: true,
32  keepAliveMsecs: 1000,
33  maxSockets: 5,
34  maxFreeSockets: 5
35});
36
37const server = http.createServer(common.mustCall((req, res) => {
38  if (req.url === '/error') {
39    res.destroy();
40    return;
41  } else if (req.url === '/remote_close') {
42    // Cache the socket, close it after a short delay
43    const socket = res.connection;
44    setImmediate(common.mustCall(() => socket.end()));
45  }
46  res.end('hello world');
47}, 4));
48
49function get(path, callback) {
50  return http.get({
51    host: 'localhost',
52    port: server.address().port,
53    agent: agent,
54    path: path
55  }, callback).on('socket', common.mustCall(checkListeners));
56}
57
58function checkDataAndSockets(body) {
59  assert.strictEqual(body.toString(), 'hello world');
60  assert.strictEqual(agent.sockets[name].length, 1);
61  assert.strictEqual(agent.freeSockets[name], undefined);
62  assert.strictEqual(agent.totalSocketCount, 1);
63}
64
65function second() {
66  // Request second, use the same socket
67  const req = get('/second', common.mustCall((res) => {
68    assert.strictEqual(req.reusedSocket, true);
69    assert.strictEqual(res.statusCode, 200);
70    res.on('data', checkDataAndSockets);
71    res.on('end', common.mustCall(() => {
72      assert.strictEqual(agent.sockets[name].length, 1);
73      assert.strictEqual(agent.freeSockets[name], undefined);
74      process.nextTick(common.mustCall(() => {
75        assert.strictEqual(agent.sockets[name], undefined);
76        assert.strictEqual(agent.freeSockets[name].length, 1);
77        assert.strictEqual(agent.totalSocketCount, 1);
78        remoteClose();
79      }));
80    }));
81  }));
82}
83
84function remoteClose() {
85  // Mock remote server close the socket
86  const req = get('/remote_close', common.mustCall((res) => {
87    assert.strictEqual(req.reusedSocket, true);
88    assert.strictEqual(res.statusCode, 200);
89    res.on('data', checkDataAndSockets);
90    res.on('end', common.mustCall(() => {
91      assert.strictEqual(agent.sockets[name].length, 1);
92      assert.strictEqual(agent.freeSockets[name], undefined);
93      process.nextTick(common.mustCall(() => {
94        assert.strictEqual(agent.sockets[name], undefined);
95        assert.strictEqual(agent.freeSockets[name].length, 1);
96        assert.strictEqual(agent.totalSocketCount, 1);
97        // Waiting remote server close the socket
98        setTimeout(common.mustCall(() => {
99          assert.strictEqual(agent.sockets[name], undefined);
100          assert.strictEqual(agent.freeSockets[name], undefined);
101          assert.strictEqual(agent.totalSocketCount, 0);
102          remoteError();
103        }), common.platformTimeout(200));
104      }));
105    }));
106  }));
107}
108
109function remoteError() {
110  // Remote server will destroy the socket
111  const req = get('/error', common.mustNotCall());
112  req.on('error', common.mustCall((err) => {
113    assert(err);
114    assert.strictEqual(err.message, 'socket hang up');
115    assert.strictEqual(agent.sockets[name].length, 1);
116    assert.strictEqual(agent.freeSockets[name], undefined);
117    assert.strictEqual(agent.totalSocketCount, 1);
118    // Wait socket 'close' event emit
119    setTimeout(common.mustCall(() => {
120      assert.strictEqual(agent.sockets[name], undefined);
121      assert.strictEqual(agent.freeSockets[name], undefined);
122      assert.strictEqual(agent.totalSocketCount, 0);
123      server.close();
124    }), common.platformTimeout(1));
125  }));
126}
127
128server.listen(0, common.mustCall(() => {
129  name = `localhost:${server.address().port}:`;
130  // Request first, and keep alive
131  const req = get('/first', common.mustCall((res) => {
132    assert.strictEqual(req.reusedSocket, false);
133    assert.strictEqual(res.statusCode, 200);
134    res.on('data', checkDataAndSockets);
135    res.on('end', common.mustCall(() => {
136      assert.strictEqual(agent.sockets[name].length, 1);
137      assert.strictEqual(agent.freeSockets[name], undefined);
138      process.nextTick(common.mustCall(() => {
139        assert.strictEqual(agent.sockets[name], undefined);
140        assert.strictEqual(agent.freeSockets[name].length, 1);
141        assert.strictEqual(agent.totalSocketCount, 1);
142        second();
143      }));
144    }));
145  }));
146}));
147
148// Check for listener leaks when reusing sockets.
149function checkListeners(socket) {
150  const callback = common.mustCall(() => {
151    if (!socket.destroyed) {
152      assert.strictEqual(socket.listenerCount('data'), 0);
153      assert.strictEqual(socket.listenerCount('drain'), 0);
154      // Sockets have freeSocketErrorListener.
155      assert.strictEqual(socket.listenerCount('error'), 1);
156      // Sockets have onReadableStreamEnd.
157      assert.strictEqual(socket.listenerCount('end'), 1);
158    }
159
160    socket.off('free', callback);
161    socket.off('close', callback);
162  });
163  assert.strictEqual(socket.listenerCount('error'), 1);
164  assert.strictEqual(socket.listenerCount('end'), 2);
165  socket.once('free', callback);
166  socket.once('close', callback);
167}
168