diff --git a/src/listener.ts b/src/listener.ts index 504c528..6a9f207 100644 --- a/src/listener.ts +++ b/src/listener.ts @@ -45,6 +45,14 @@ const handleResponseError = (e: unknown, outgoing: ServerResponse | Http2ServerR } } +const flushHeaders = (outgoing: ServerResponse | Http2ServerResponse) => { + // If outgoing is ServerResponse (HTTP/1.1), it requires this to flush headers. + // However, Http2ServerResponse is sent without this. + if ('flushHeaders' in outgoing && outgoing.writable) { + outgoing.flushHeaders() + } +} + const responseViaCache = async ( res: Response, outgoing: ServerResponse | Http2ServerResponse @@ -69,6 +77,7 @@ const responseViaCache = async ( } else if (body instanceof Blob) { outgoing.end(new Uint8Array(await body.arrayBuffer())) } else { + flushHeaders(outgoing) return writeFromReadableStream(body, outgoing)?.catch( (e) => handleResponseError(e, outgoing) as undefined ) @@ -129,6 +138,7 @@ const responseViaResponseObject = async ( !regContentType.test(contentType as string) ) { outgoing.writeHead(res.status, resHeaderRecord) + flushHeaders(outgoing) await writeFromReadableStream(res.body, outgoing) } else { diff --git a/test/server.test.ts b/test/server.test.ts index 745efc9..1d524f5 100644 --- a/test/server.test.ts +++ b/test/server.test.ts @@ -134,9 +134,14 @@ describe('various response body types', () => { }) return response }) + let resolveReadableStreamPromise: () => void + const readableStreamPromise = new Promise((resolve) => { + resolveReadableStreamPromise = resolve + }) app.get('/readable-stream', () => { const stream = new ReadableStream({ async start(controller) { + await readableStreamPromise controller.enqueue('Hello!') controller.enqueue(' Node!') controller.close() @@ -187,15 +192,21 @@ describe('various response body types', () => { it('Should return 200 response - GET /readable-stream', async () => { const expectedChunks = ['Hello!', ' Node!'] - const res = await request(server) + const resPromise = request(server) .get('/readable-stream') .parse((res, fn) => { + // response header should be sent before sending data. + expect(res.headers['transfer-encoding']).toBe('chunked') + resolveReadableStreamPromise() + res.on('data', (chunk) => { const str = chunk.toString() expect(str).toBe(expectedChunks.shift()) }) res.on('end', () => fn(null, '')) }) + await new Promise((resolve) => setTimeout(resolve, 100)) + const res = await resPromise expect(res.status).toBe(200) expect(res.headers['content-type']).toMatch('text/plain; charset=UTF-8') expect(res.headers['content-length']).toBeUndefined()