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