Skip to content

Commit 9741537

Browse files
Skn0tttoken-generator-app[bot]tinfoil-knight
authored
fix: ensure HMR requests can be streamed (#5200)
* feat: add mode for streamed responses * chore: update contributors field * chore: update contributors field * fix: prettier * chore: update contributors field * chore: update contributors field * Update src/utils/proxy.cjs Co-authored-by: Kunal Kundu <kunal99kundu@gmail.com> * chore: update contributors field * chore: update contributors field * fix: write test to ensure edge functions can be streamed * chore: update contributors field * fix: remove .only * chore: update contributors field * chore: update contributors field * chore: update contributors field * fix: snapshots (see #5194 (comment)) * chore: update contributors field * chore: update contributors field * fix: windows doesn't like overriding sites * chore: update contributors field * chore: update contributors field Co-authored-by: Skn0tt <Skn0tt@users.noreply.github.com> Co-authored-by: token-generator-app[bot] <token-generator-app[bot]@users.noreply.github.com> Co-authored-by: Kunal Kundu <kunal99kundu@gmail.com>
1 parent 1ab07f0 commit 9741537

File tree

5 files changed

+86
-1
lines changed

5 files changed

+86
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"Krasimir Nedelchev (https://github.com/kaykayehnn)",
8080
"Kunal Kundu (https://twitter.com/kunal__kundu)",
8181
"Kyle Rollins <kyleblankrollins@gmail.com> (kyleblankrollins.com)",
82+
"Lana Vorozheykina",
8283
"Leon Hennings",
8384
"Liran Tal <liran.tal@gmail.com> (https://twitter.com/liran_tal)",
8485
"Louis DeScioli (https://twitter.com/descioli)",

src/utils/proxy.cjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,26 @@ const initializeProxy = async function ({ configPath, distDir, host, port, proje
379379
const requestURL = new URL(req.url, `http://${req.headers.host || '127.0.0.1'}`)
380380
const headersRules = headersForPath(headers, requestURL.pathname)
381381

382+
// for streamed responses, we can't do etag generation nor error templates.
383+
// we'll just stream them through!
384+
const isStreamedResponse = proxyRes.headers['content-length'] === undefined
385+
if (isStreamedResponse) {
386+
Object.entries(headersRules).forEach(([key, val]) => {
387+
res.setHeader(key, val)
388+
})
389+
res.writeHead(req.proxyOptions.status || proxyRes.statusCode, proxyRes.headers)
390+
391+
proxyRes.on('data', function onData(data) {
392+
res.write(data)
393+
})
394+
395+
proxyRes.on('end', function onEnd() {
396+
res.end()
397+
})
398+
399+
return
400+
}
401+
382402
proxyRes.on('data', function onData(data) {
383403
responseData.push(data)
384404
})

tests/integration/100.command.dev.test.cjs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,70 @@ test('Serves an Edge Function that transforms the response', async (t) => {
420420
})
421421
})
422422

423+
test('Serves an Edge Function that streams the response', async (t) => {
424+
await withSiteBuilder('site-with-edge-function-that-streams-response', async (builder) => {
425+
const publicDir = 'public'
426+
builder
427+
.withNetlifyToml({
428+
config: {
429+
build: {
430+
publish: publicDir,
431+
edge_functions: 'netlify/edge-functions',
432+
},
433+
edge_functions: [
434+
{
435+
function: 'stream',
436+
path: '/stream',
437+
},
438+
],
439+
},
440+
})
441+
.withEdgeFunction({
442+
handler: async () => {
443+
// eslint-disable-next-line no-undef -- `ReadableStream` is a global in Deno
444+
const body = new ReadableStream({
445+
async start(controller) {
446+
setInterval(() => {
447+
const msg = new TextEncoder().encode(`${Date.now()}\r\n`)
448+
controller.enqueue(msg)
449+
}, 100)
450+
451+
setTimeout(() => {
452+
controller.close()
453+
}, 500)
454+
},
455+
})
456+
457+
return new Response(body, {
458+
headers: {
459+
'content-type': 'text/event-stream',
460+
},
461+
status: 200,
462+
})
463+
},
464+
name: 'stream',
465+
})
466+
467+
await builder.buildAsync()
468+
469+
await withDevServer({ cwd: builder.directory }, async (server) => {
470+
let numberOfChunks = 0
471+
472+
await new Promise((resolve, reject) => {
473+
const stream = got.stream(`${server.url}/stream`)
474+
stream.on('data', () => {
475+
numberOfChunks += 1
476+
})
477+
stream.on('end', resolve)
478+
stream.on('error', reject)
479+
})
480+
481+
// streamed responses arrive in more than one batch
482+
t.not(numberOfChunks, 1)
483+
})
484+
})
485+
})
486+
423487
test('redirect with country cookie', async (t) => {
424488
await withSiteBuilder('site-with-country-cookie', async (builder) => {
425489
builder

tests/integration/snapshots/320.command.help.test.cjs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Generated by [AVA](https://avajs.dev).
2121
$ completion (Beta) Generate shell completion script␊
2222
$ deploy Create a new deploy from the contents of a folder␊
2323
$ dev Local dev server␊
24-
$ env (Beta) Control environment variables for the current site␊
24+
$ env Control environment variables for the current site␊
2525
$ functions Manage netlify functions␊
2626
$ recipes (Beta) Create and modify files in a project using pre-defined␊
2727
recipes␊
Binary file not shown.

0 commit comments

Comments
 (0)