|
| 1 | +// Flags: --experimental-quic --experimental-stream-iter --no-warnings |
| 2 | + |
| 3 | +// Test: HTTP/3 webtransport session establishment |
| 4 | +// Verifies that a session established and properly closed |
| 5 | + |
| 6 | +import { hasQuic, skip, mustCall } from '../common/index.mjs'; |
| 7 | +import assert from 'node:assert'; |
| 8 | +import * as fixtures from '../common/fixtures.mjs'; |
| 9 | + |
| 10 | +if (!hasQuic) { |
| 11 | + skip('QUIC is not enabled'); |
| 12 | +} |
| 13 | + |
| 14 | +const { listen, connect } = await import('node:quic'); |
| 15 | +const { createPrivateKey } = await import('node:crypto'); |
| 16 | +const { setTimeout: sleep } = await import('timers/promises'); |
| 17 | + |
| 18 | +const key = createPrivateKey(fixtures.readKey('agent1-key.pem')); |
| 19 | +const cert = fixtures.readKey('agent1-cert.pem'); |
| 20 | + |
| 21 | +const serverSessionOpened = Promise.withResolvers(); |
| 22 | + |
| 23 | + |
| 24 | +const serverEndpoint = await listen(mustCall(async (ss) => { |
| 25 | + ss.onapplication = mustCall((aopts) => { |
| 26 | + assert.strictEqual(!aopts.enableDatagrams, false); |
| 27 | + }); |
| 28 | + ss.onhandshake = mustCall(function() { |
| 29 | + assert.strictEqual(BigInt(this?.remoteTransportParams?.maxDatagramFrameSize) > 0, true); |
| 30 | + }); |
| 31 | +}), { |
| 32 | + sni: { '*': { keys: [key], certs: [cert] } }, |
| 33 | + application: { |
| 34 | + enableConnectProtocol: true, |
| 35 | + enableDatagrams: true, |
| 36 | + enableWebtransport: true |
| 37 | + }, |
| 38 | + transportParams: { maxDatagramFrameSize: 100 }, |
| 39 | + onheaders: mustCall(function(headers) { |
| 40 | + try { |
| 41 | + assert.strictEqual(headers[':scheme'], 'https'); |
| 42 | + assert.strictEqual(headers[':method'], 'CONNECT'); |
| 43 | + assert.strictEqual(headers[':protocol'], 'webtransport'); // depends on the draft |
| 44 | + assert.strictEqual(headers[':path'], '/testwtpath'); |
| 45 | + // We could also check for wt-available protocols |
| 46 | + this.sendHeaders( |
| 47 | + { ':status': '200' }, |
| 48 | + { terminal: false, webtransport: true } |
| 49 | + ); |
| 50 | + serverSessionOpened.resolve(); |
| 51 | + } catch (error) { |
| 52 | + serverSessionOpened.reject(error); |
| 53 | + } |
| 54 | + // Should only be installed on wt streams |
| 55 | + this.onwtsessionclose = mustCall((code, reason) => { |
| 56 | + assert.strictEqual(code, 200); |
| 57 | + assert.strictEqual(reason, 'all perfect'); |
| 58 | + this.session.close(); |
| 59 | + }); |
| 60 | + }), |
| 61 | +}); |
| 62 | + |
| 63 | +const clientSession = await connect(serverEndpoint.address, { |
| 64 | + servername: 'localhost', |
| 65 | + verifyPeer: 'manual', |
| 66 | + application: { |
| 67 | + enableConnectProtocol: true, |
| 68 | + enableDatagrams: true, |
| 69 | + enableWebtransport: true |
| 70 | + }, |
| 71 | + transportParams: { maxDatagramFrameSize: 1000 }, |
| 72 | +}); |
| 73 | + |
| 74 | +const webtransportSupport = Promise.withResolvers(); |
| 75 | + |
| 76 | +clientSession.onapplication = mustCall((aopts) => { |
| 77 | + try { |
| 78 | + // Test for webtransport support |
| 79 | + assert.strictEqual(aopts.enableConnectProtocol, true); |
| 80 | + assert.strictEqual(aopts.enableDatagrams, true); |
| 81 | + assert.strictEqual(aopts.enableWebtransport, true); |
| 82 | + // Ok we have wt support |
| 83 | + webtransportSupport.resolve(); |
| 84 | + } catch (error) { |
| 85 | + webtransportSupport.reject(error); |
| 86 | + } |
| 87 | +}); |
| 88 | + |
| 89 | +clientSession.onstream = mustCall((stream) => { |
| 90 | + stream.onheaders = mustCall((stream) => { |
| 91 | + // Well this should not happen on client side |
| 92 | + assert.strictEqual(false, true); |
| 93 | + }); |
| 94 | +}); |
| 95 | + |
| 96 | +await clientSession.opened; |
| 97 | +await webtransportSupport.promise; |
| 98 | +// Now we open a webtransport session, which is actually |
| 99 | +// a special bidirectional stream |
| 100 | +const wtSessionStream = await clientSession.createBidirectionalStream({ |
| 101 | + body: '', |
| 102 | +}); |
| 103 | +wtSessionStream.sendHeaders({ |
| 104 | + ':method': 'CONNECT', |
| 105 | + ':scheme': 'https', |
| 106 | + // This one depends on draft, draft14 says "webtransport", draft15 says "webtransport-h3" |
| 107 | + ':protocol': 'webtransport', |
| 108 | + ':path': '/testwtpath', |
| 109 | + ':authority': 'testserver:' + serverEndpoint.address.port |
| 110 | +}, { |
| 111 | + webtransport: true // Tell nghttp3 to treat the stream as a WT session stream |
| 112 | +}); |
| 113 | + |
| 114 | +await serverSessionOpened.promise; |
| 115 | +await sleep(500); |
| 116 | +await wtSessionStream.closeWebtransportSessionStream(200, 'all perfect'); |
| 117 | + |
| 118 | +try { |
| 119 | + await wtSessionStream.closed; |
| 120 | +} catch (error) { |
| 121 | + assert.strictEqual(error.errorCode, 200n); |
| 122 | + assert.strictEqual(error.reason, 'all perfect'); |
| 123 | +} |
| 124 | +await clientSession.close(); |
| 125 | +await serverEndpoint.close(); |
0 commit comments