• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Flags: --expose-internals
2
3'use strict';
4
5const common = require('../common');
6if (!common.hasCrypto)
7  common.skip('missing crypto');
8const assert = require('assert');
9const h2 = require('http2');
10const { kSocket } = require('internal/http2/util');
11const Countdown = require('../common/countdown');
12
13{
14  const server = h2.createServer();
15  server.listen(0, common.mustCall(() => {
16    const destroyCallbacks = [
17      (client) => client.destroy(),
18      (client) => client[kSocket].destroy()
19    ];
20
21    const countdown = new Countdown(destroyCallbacks.length, () => {
22      server.close();
23    });
24
25    destroyCallbacks.forEach((destroyCallback) => {
26      const client = h2.connect(`http://localhost:${server.address().port}`);
27      client.on('connect', common.mustCall(() => {
28        const socket = client[kSocket];
29
30        assert(socket, 'client session has associated socket');
31        assert(
32          !client.destroyed,
33          'client has not been destroyed before destroy is called'
34        );
35        assert(
36          !socket.destroyed,
37          'socket has not been destroyed before destroy is called'
38        );
39
40        destroyCallback(client);
41
42        client.on('close', common.mustCall(() => {
43          assert(client.destroyed);
44        }));
45
46        countdown.dec();
47      }));
48    });
49  }));
50}
51
52// Test destroy before client operations
53{
54  const server = h2.createServer();
55  server.listen(0, common.mustCall(() => {
56    const client = h2.connect(`http://localhost:${server.address().port}`);
57    const socket = client[kSocket];
58    socket.on('close', common.mustCall(() => {
59      assert(socket.destroyed);
60    }));
61
62    const req = client.request();
63    req.on('error', common.expectsError({
64      code: 'ERR_HTTP2_STREAM_CANCEL',
65      name: 'Error',
66      message: 'The pending stream has been canceled'
67    }));
68
69    client.destroy();
70
71    req.on('response', common.mustNotCall());
72
73    const sessionError = {
74      name: 'Error',
75      code: 'ERR_HTTP2_INVALID_SESSION',
76      message: 'The session has been destroyed'
77    };
78
79    assert.throws(() => client.setNextStreamID(), sessionError);
80    assert.throws(() => client.ping(), sessionError);
81    assert.throws(() => client.settings({}), sessionError);
82    assert.throws(() => client.goaway(), sessionError);
83    assert.throws(() => client.request(), sessionError);
84    client.close();  // Should be a non-op at this point
85
86    // Wait for setImmediate call from destroy() to complete
87    // so that state.destroyed is set to true
88    setImmediate(() => {
89      assert.throws(() => client.setNextStreamID(), sessionError);
90      assert.throws(() => client.ping(), sessionError);
91      assert.throws(() => client.settings({}), sessionError);
92      assert.throws(() => client.goaway(), sessionError);
93      assert.throws(() => client.request(), sessionError);
94      client.close();  // Should be a non-op at this point
95    });
96
97    req.resume();
98    req.on('end', common.mustCall());
99    req.on('close', common.mustCall(() => server.close()));
100  }));
101}
102
103// Test destroy before goaway
104{
105  const server = h2.createServer();
106  server.on('stream', common.mustCall((stream) => {
107    stream.session.destroy();
108  }));
109
110  server.listen(0, common.mustCall(() => {
111    const client = h2.connect(`http://localhost:${server.address().port}`);
112
113    client.on('close', () => {
114      server.close();
115      // Calling destroy in here should not matter
116      client.destroy();
117    });
118
119    client.request();
120  }));
121}
122
123// Test destroy before connect
124{
125  const server = h2.createServer();
126  server.on('stream', common.mustNotCall());
127
128  server.listen(0, common.mustCall(() => {
129    const client = h2.connect(`http://localhost:${server.address().port}`);
130
131    server.on('connection', common.mustCall(() => {
132      server.close();
133      client.close();
134    }));
135
136    const req = client.request();
137    req.destroy();
138  }));
139}
140
141// Test close before connect
142{
143  const server = h2.createServer();
144
145  server.on('stream', common.mustNotCall());
146  server.listen(0, common.mustCall(() => {
147    const client = h2.connect(`http://localhost:${server.address().port}`);
148    client.on('close', common.mustCall());
149    const socket = client[kSocket];
150    socket.on('close', common.mustCall(() => {
151      assert(socket.destroyed);
152    }));
153
154    const req = client.request();
155    // Should throw goaway error
156    req.on('error', common.expectsError({
157      code: 'ERR_HTTP2_GOAWAY_SESSION',
158      name: 'Error',
159      message: 'New streams cannot be created after receiving a GOAWAY'
160    }));
161
162    client.close();
163    req.resume();
164    req.on('end', common.mustCall());
165    req.on('close', common.mustCall(() => server.close()));
166  }));
167}
168