• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}
63
64function second() {
65  // Request second, use the same socket
66  const req = get('/second', common.mustCall((res) => {
67    assert.strictEqual(req.reusedSocket, true);
68    assert.strictEqual(res.statusCode, 200);
69    res.on('data', checkDataAndSockets);
70    res.on('end', common.mustCall(() => {
71      assert.strictEqual(agent.sockets[name].length, 1);
72      assert.strictEqual(agent.freeSockets[name], undefined);
73      process.nextTick(common.mustCall(() => {
74        assert.strictEqual(agent.sockets[name], undefined);
75        assert.strictEqual(agent.freeSockets[name].length, 1);
76        remoteClose();
77      }));
78    }));
79  }));
80}
81
82function remoteClose() {
83  // Mock remote server close the socket
84  const req = get('/remote_close', common.mustCall((res) => {
85    assert.deepStrictEqual(req.reusedSocket, true);
86    assert.deepStrictEqual(res.statusCode, 200);
87    res.on('data', checkDataAndSockets);
88    res.on('end', common.mustCall(() => {
89      assert.strictEqual(agent.sockets[name].length, 1);
90      assert.strictEqual(agent.freeSockets[name], undefined);
91      process.nextTick(common.mustCall(() => {
92        assert.strictEqual(agent.sockets[name], undefined);
93        assert.strictEqual(agent.freeSockets[name].length, 1);
94        // Waiting remote server close the socket
95        setTimeout(common.mustCall(() => {
96          assert.strictEqual(agent.sockets[name], undefined);
97          assert.strictEqual(agent.freeSockets[name], undefined);
98          remoteError();
99        }), common.platformTimeout(200));
100      }));
101    }));
102  }));
103}
104
105function remoteError() {
106  // Remote server will destroy the socket
107  const req = get('/error', common.mustNotCall());
108  req.on('error', common.mustCall((err) => {
109    assert(err);
110    assert.strictEqual(err.message, 'socket hang up');
111    assert.strictEqual(agent.sockets[name].length, 1);
112    assert.strictEqual(agent.freeSockets[name], undefined);
113    // Wait socket 'close' event emit
114    setTimeout(common.mustCall(() => {
115      assert.strictEqual(agent.sockets[name], undefined);
116      assert.strictEqual(agent.freeSockets[name], undefined);
117      server.close();
118    }), common.platformTimeout(1));
119  }));
120}
121
122server.listen(0, common.mustCall(() => {
123  name = `localhost:${server.address().port}:`;
124  // Request first, and keep alive
125  const req = get('/first', common.mustCall((res) => {
126    assert.strictEqual(req.reusedSocket, false);
127    assert.strictEqual(res.statusCode, 200);
128    res.on('data', checkDataAndSockets);
129    res.on('end', common.mustCall(() => {
130      assert.strictEqual(agent.sockets[name].length, 1);
131      assert.strictEqual(agent.freeSockets[name], undefined);
132      process.nextTick(common.mustCall(() => {
133        assert.strictEqual(agent.sockets[name], undefined);
134        assert.strictEqual(agent.freeSockets[name].length, 1);
135        second();
136      }));
137    }));
138  }));
139}));
140
141// Check for listener leaks when reusing sockets.
142function checkListeners(socket) {
143  const callback = common.mustCall(() => {
144    if (!socket.destroyed) {
145      assert.strictEqual(socket.listenerCount('data'), 0);
146      assert.strictEqual(socket.listenerCount('drain'), 0);
147      // Sockets have freeSocketErrorListener.
148      assert.strictEqual(socket.listenerCount('error'), 1);
149      // Sockets have onReadableStreamEnd.
150      assert.strictEqual(socket.listenerCount('end'), 1);
151    }
152
153    socket.off('free', callback);
154    socket.off('close', callback);
155  });
156  assert.strictEqual(socket.listenerCount('error'), 1);
157  assert.strictEqual(socket.listenerCount('end'), 2);
158  socket.once('free', callback);
159  socket.once('close', callback);
160}
161