20486c159d
* [edge] allow importing blob assets * Fix test * extract to a new file, to make it easier to read and review * Use webpack asset discovery and transform with a loader * fix tests * don't prefix assets * use emitFile * rename assets to blobs to be more specific * rename blobs to assets and use webpack's hashing algo * Dedupe correctly * Add a Node.js dep test * Update packages/next/server/next-server.ts Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com> * [code review] test remote URL fetches * [code review] use `import type` for type-only imports * Update packages/next/server/next-server.ts Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com> * Apply suggestions from code review Co-authored-by: JJ Kasper <jj@jjsweb.site> Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com> Co-authored-by: JJ Kasper <jj@jjsweb.site>
93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
import Primitives from 'next/dist/compiled/@edge-runtime/primitives'
|
|
import type { IncomingMessage } from 'http'
|
|
import { Readable } from 'stream'
|
|
|
|
type BodyStream = ReadableStream<Uint8Array>
|
|
|
|
/**
|
|
* Creates a ReadableStream from a Node.js HTTP request
|
|
*/
|
|
export function requestToBodyStream(request: Readable): BodyStream {
|
|
const transform = new Primitives.TransformStream<Uint8Array, Uint8Array>({
|
|
start(controller) {
|
|
request.on('data', (chunk) => controller.enqueue(chunk))
|
|
request.on('end', () => controller.terminate())
|
|
request.on('error', (err) => controller.error(err))
|
|
},
|
|
})
|
|
|
|
return transform.readable as unknown as ReadableStream<Uint8Array>
|
|
}
|
|
|
|
export function bodyStreamToNodeStream(bodyStream: BodyStream): Readable {
|
|
const reader = bodyStream.getReader()
|
|
return Readable.from(
|
|
(async function* () {
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) {
|
|
return
|
|
}
|
|
yield value
|
|
}
|
|
})()
|
|
)
|
|
}
|
|
|
|
function replaceRequestBody<T extends IncomingMessage>(
|
|
base: T,
|
|
stream: Readable
|
|
): T {
|
|
for (const key in stream) {
|
|
let v = stream[key as keyof Readable] as any
|
|
if (typeof v === 'function') {
|
|
v = v.bind(base)
|
|
}
|
|
base[key as keyof T] = v
|
|
}
|
|
|
|
return base
|
|
}
|
|
|
|
/**
|
|
* An interface that encapsulates body stream cloning
|
|
* of an incoming request.
|
|
*/
|
|
export function clonableBodyForRequest<T extends IncomingMessage>(
|
|
incomingMessage: T
|
|
) {
|
|
let bufferedBodyStream: BodyStream | null = null
|
|
|
|
const endPromise = new Promise((resolve, reject) => {
|
|
incomingMessage.on('end', resolve)
|
|
incomingMessage.on('error', reject)
|
|
})
|
|
|
|
return {
|
|
/**
|
|
* Replaces the original request body if necessary.
|
|
* This is done because once we read the body from the original request,
|
|
* we can't read it again.
|
|
*/
|
|
async finalize(): Promise<void> {
|
|
if (bufferedBodyStream) {
|
|
await endPromise
|
|
replaceRequestBody(
|
|
incomingMessage,
|
|
bodyStreamToNodeStream(bufferedBodyStream)
|
|
)
|
|
}
|
|
},
|
|
/**
|
|
* Clones the body stream
|
|
* to pass into a middleware
|
|
*/
|
|
cloneBodyStream(): BodyStream {
|
|
const originalStream =
|
|
bufferedBodyStream ?? requestToBodyStream(incomingMessage)
|
|
const [stream1, stream2] = originalStream.tee()
|
|
bufferedBodyStream = stream1
|
|
return stream2
|
|
},
|
|
}
|
|
}
|