mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 13:19:09 +03:00
fix(studio/serve): pipe Response body via Readable.fromWeb
`for await (chunk of response.body)` works for simple responses but can leave the web ReadableStream in a "disturbed/locked" state when the TanStack Start handler peeks at the body internally, surfacing on the next request as TypeError: Response body object should not be disturbed or locked Switch to `Readable.fromWeb(response.body).pipe(res)` so the stream is consumed via Node's stream machinery with backpressure and proper cleanup. Resolves the Promise on `res.close`/`finish` and propagates errors from either end.
This commit is contained in:
1 parent
868958b16e
commit
0bd9f1bb2d
1 file changed
+24
-11
@@ -15,13 +15,12 @@
|
||||
// right MIME types and cache headers.
|
||||
// - Forward everything else to the TanStack Start handler exported
|
||||
// from `dist/server/server.js`.
|
||||
|
||||
import { createServer } from 'node:http'
|
||||
import { createReadStream } from 'node:fs'
|
||||
import { readFile, stat } from 'node:fs/promises'
|
||||
import { createServer } from 'node:http'
|
||||
import path from 'node:path'
|
||||
import { Readable } from 'node:stream'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'node:path'
|
||||
|
||||
const studioRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const clientDir = path.join(studioRoot, 'dist/client')
|
||||
@@ -41,10 +40,7 @@ for (const file of envFiles) {
|
||||
const m = dotenvLine.exec(raw)
|
||||
if (!m) continue
|
||||
let v = m[2]
|
||||
if (
|
||||
(v.startsWith('"') && v.endsWith('"')) ||
|
||||
(v.startsWith("'") && v.endsWith("'"))
|
||||
) {
|
||||
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
||||
v = v.slice(1, -1)
|
||||
}
|
||||
parsed[m[1]] = v
|
||||
@@ -105,7 +101,10 @@ async function serveStatic(req, res) {
|
||||
if (!st.isFile()) return false
|
||||
|
||||
res.statusCode = 200
|
||||
res.setHeader('content-type', mimeByExt.get(path.extname(filePath).toLowerCase()) ?? 'application/octet-stream')
|
||||
res.setHeader(
|
||||
'content-type',
|
||||
mimeByExt.get(path.extname(filePath).toLowerCase()) ?? 'application/octet-stream'
|
||||
)
|
||||
res.setHeader('content-length', String(st.size))
|
||||
res.setHeader(
|
||||
'cache-control',
|
||||
@@ -140,10 +139,24 @@ function toWebRequest(req) {
|
||||
async function pipeWebResponse(response, res) {
|
||||
res.statusCode = response.status
|
||||
for (const [k, v] of response.headers) res.setHeader(k, v)
|
||||
if (response.body) {
|
||||
for await (const chunk of response.body) res.write(chunk)
|
||||
if (!response.body) {
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
res.end()
|
||||
// Pipe via Readable.fromWeb so the underlying stream gets proper backpressure
|
||||
// and gets released cleanly. `for await (chunk of response.body)` works in
|
||||
// simple cases but can leave the body in a "disturbed / locked" state when
|
||||
// the handler internally peeks at it — surfacing as
|
||||
// `TypeError: Response body object should not be disturbed or locked` on a
|
||||
// subsequent request.
|
||||
await new Promise((resolve, reject) => {
|
||||
const readable = Readable.fromWeb(response.body)
|
||||
readable.on('error', reject)
|
||||
res.on('error', reject)
|
||||
res.on('close', resolve)
|
||||
res.on('finish', resolve)
|
||||
readable.pipe(res)
|
||||
})
|
||||
}
|
||||
|
||||
const port = Number(process.env.PORT || 8082)
|
||||
|
||||
Reference in new issue
Block a user