Skip to content

Commit 69d274d

Browse files
authored
fix: flush headers before sending data via readable stream (#245)
1 parent 6a1e4db commit 69d274d

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

src/listener.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ const handleResponseError = (e: unknown, outgoing: ServerResponse | Http2ServerR
4545
}
4646
}
4747

48+
const flushHeaders = (outgoing: ServerResponse | Http2ServerResponse) => {
49+
// If outgoing is ServerResponse (HTTP/1.1), it requires this to flush headers.
50+
// However, Http2ServerResponse is sent without this.
51+
if ('flushHeaders' in outgoing && outgoing.writable) {
52+
outgoing.flushHeaders()
53+
}
54+
}
55+
4856
const responseViaCache = async (
4957
res: Response,
5058
outgoing: ServerResponse | Http2ServerResponse
@@ -69,6 +77,7 @@ const responseViaCache = async (
6977
} else if (body instanceof Blob) {
7078
outgoing.end(new Uint8Array(await body.arrayBuffer()))
7179
} else {
80+
flushHeaders(outgoing)
7281
return writeFromReadableStream(body, outgoing)?.catch(
7382
(e) => handleResponseError(e, outgoing) as undefined
7483
)
@@ -129,6 +138,7 @@ const responseViaResponseObject = async (
129138
!regContentType.test(contentType as string)
130139
) {
131140
outgoing.writeHead(res.status, resHeaderRecord)
141+
flushHeaders(outgoing)
132142

133143
await writeFromReadableStream(res.body, outgoing)
134144
} else {

test/server.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,14 @@ describe('various response body types', () => {
134134
})
135135
return response
136136
})
137+
let resolveReadableStreamPromise: () => void
138+
const readableStreamPromise = new Promise<void>((resolve) => {
139+
resolveReadableStreamPromise = resolve
140+
})
137141
app.get('/readable-stream', () => {
138142
const stream = new ReadableStream({
139143
async start(controller) {
144+
await readableStreamPromise
140145
controller.enqueue('Hello!')
141146
controller.enqueue(' Node!')
142147
controller.close()
@@ -187,15 +192,21 @@ describe('various response body types', () => {
187192

188193
it('Should return 200 response - GET /readable-stream', async () => {
189194
const expectedChunks = ['Hello!', ' Node!']
190-
const res = await request(server)
195+
const resPromise = request(server)
191196
.get('/readable-stream')
192197
.parse((res, fn) => {
198+
// response header should be sent before sending data.
199+
expect(res.headers['transfer-encoding']).toBe('chunked')
200+
resolveReadableStreamPromise()
201+
193202
res.on('data', (chunk) => {
194203
const str = chunk.toString()
195204
expect(str).toBe(expectedChunks.shift())
196205
})
197206
res.on('end', () => fn(null, ''))
198207
})
208+
await new Promise((resolve) => setTimeout(resolve, 100))
209+
const res = await resPromise
199210
expect(res.status).toBe(200)
200211
expect(res.headers['content-type']).toMatch('text/plain; charset=UTF-8')
201212
expect(res.headers['content-length']).toBeUndefined()

0 commit comments

Comments
 (0)