UNPKG

@fastify/reply-from

Version:

forward your HTTP request to another server, for fastify

127 lines (103 loc) 3.82 kB
'use strict' const h2url = require('h2url') const t = require('node:test') const Fastify = require('fastify') const From = require('..') const fs = require('node:fs') const path = require('node:path') const http2 = require('node:http2') const certs = { key: fs.readFileSync(path.join(__dirname, 'fixtures', 'fastify.key')), cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'fastify.cert')) } t.test('http2 goaway handling - reproduces issue #409', async (t) => { let requestCount = 0 // Create a custom HTTP/2 server that sends GOAWAY after first request const targetServer = http2.createServer() let sessionToClose = null targetServer.on('session', (session) => { // Store the first session to send GOAWAY later if (!sessionToClose) { sessionToClose = session } }) targetServer.on('stream', (stream, headers) => { requestCount++ if (requestCount === 1) { // First request: respond normally stream.respond({ ':status': 200, 'content-type': 'application/json' }) stream.end(JSON.stringify({ request: requestCount, message: 'first request' })) // Send GOAWAY after response to close the HTTP/2 session gracefully setTimeout(() => { if (sessionToClose && !sessionToClose.destroyed) { // Send GOAWAY with NO_ERROR to close gracefully sessionToClose.goaway(0) } }, 50) } else { // Subsequent requests should work with a new session stream.respond({ ':status': 200, 'content-type': 'application/json' }) stream.end(JSON.stringify({ request: requestCount, message: 'subsequent request' })) } }) await new Promise((resolve) => { targetServer.listen(0, resolve) }) const targetPort = targetServer.address().port // Create proxy server const instance = Fastify({ http2: true, https: certs }) instance.register(From, { base: `http://localhost:${targetPort}`, http2: true, rejectUnauthorized: false }) instance.get('/', (_request, reply) => { reply.from() }) await instance.listen({ port: 0 }) const proxyPort = instance.server.address().port // First request - should succeed const firstResponse = await h2url.concat({ url: `https://localhost:${proxyPort}` }) t.assert.strictEqual(firstResponse.headers[':status'], 200) const firstBody = JSON.parse(firstResponse.body) t.assert.strictEqual(firstBody.request, 1) t.assert.strictEqual(firstBody.message, 'first request') // Wait for GOAWAY to be sent and processed await new Promise(resolve => setTimeout(resolve, 100)) // Second request - this should fail with current implementation but work with fix try { const secondResponse = await h2url.concat({ url: `https://localhost:${proxyPort}`, timeout: 1000 }) // If we get here with the current code, the request succeeded // which means the issue might not be reproduced t.assert.strictEqual(secondResponse.headers[':status'], 200) const secondBody = JSON.parse(secondResponse.body) t.assert.strictEqual(secondBody.request, 2) t.assert.strictEqual(secondBody.message, 'subsequent request') console.log('Second request succeeded - issue may not be reproduced or fix is already in place') } catch (err) { // This is expected without the fix - the session is stuck in closed state console.log(`Second request failed (expected without fix): ${err.code || err.message}`) // This demonstrates the issue exists - session is stuck after GOAWAY } // Cleanup in correct order: clients first, then proxy, then server await instance.close() targetServer.close() // Force exit after a short delay to ensure test completes setTimeout(() => { process.exit(0) }, 100) })