-
Notifications
You must be signed in to change notification settings - Fork 131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add a cloudflare-streaming wrapper #642
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@opennextjs/aws": minor | ||
--- | ||
|
||
feat: add a cloudflare-streaming wrapper |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import type { InternalEvent, InternalResult } from "types/open-next"; | ||
import type { WrapperHandler } from "types/overrides"; | ||
|
||
import { Writable } from "node:stream"; | ||
import type { StreamCreator } from "http/index"; | ||
import type { MiddlewareOutputEvent } from "../../core/routingHandler"; | ||
|
||
const handler: WrapperHandler< | ||
InternalEvent, | ||
InternalResult | ({ type: "middleware" } & MiddlewareOutputEvent) | ||
> = | ||
async (handler, converter) => | ||
async ( | ||
request: Request, | ||
env: Record<string, string>, | ||
ctx: any, | ||
): Promise<Response> => { | ||
globalThis.process = process; | ||
globalThis.openNextWaitUntil = ctx.waitUntil.bind(ctx); | ||
|
||
// Set the environment variables | ||
// Cloudflare suggests to not override the process.env object but instead apply the values to it | ||
for (const [key, value] of Object.entries(env)) { | ||
if (typeof value === "string") { | ||
process.env[key] = value; | ||
} | ||
} | ||
|
||
const internalEvent = await converter.convertFrom(request); | ||
|
||
// TODO: | ||
// The edge converter populate event.url with the url including the origin. | ||
// This is required for middleware to keep track of the protocol (i.e. http with wrangler dev). | ||
// However the server expects that the origin is not included. | ||
const url = new URL(internalEvent.url); | ||
(internalEvent.url as string) = url.href.slice(url.origin.length); | ||
|
||
const { promise: promiseResponse, resolve: resolveResponse } = | ||
Promise.withResolvers<Response>(); | ||
|
||
const streamCreator: StreamCreator = { | ||
writeHeaders(prelude: { | ||
statusCode: number; | ||
cookies: string[]; | ||
headers: Record<string, string>; | ||
}): Writable { | ||
const { statusCode, cookies, headers } = prelude; | ||
|
||
const responseHeaders = new Headers(headers); | ||
for (const cookie of cookies) { | ||
responseHeaders.append("Set-Cookie", cookie); | ||
} | ||
|
||
const { readable, writable } = new TransformStream(); | ||
const response = new Response(readable, { | ||
status: statusCode, | ||
headers: responseHeaders, | ||
}); | ||
resolveResponse(response); | ||
|
||
return Writable.fromWeb(writable); | ||
}, | ||
onWrite: () => {}, | ||
onFinish: (_length: number) => {}, | ||
}; | ||
|
||
ctx.waitUntil(handler(internalEvent, streamCreator)); | ||
|
||
return promiseResponse; | ||
}; | ||
|
||
export default { | ||
wrapper: handler, | ||
name: "cloudflare-streaming", | ||
supportStreaming: true, | ||
edgeRuntime: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not entirely sure what the value should be here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this value without paying too much attention but I agree it should be false. |
||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this be necessary to import process here ?
Something like
import * as process from 'node:process';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
process
is a global in node compat mode so I think this should be dropped entirely. This code would only make sense when not in node compat mode.