From 5ecfc3ed4401d6ee10f403c40420c61e96999f9a Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Thu, 14 Nov 2024 12:51:59 +0100 Subject: [PATCH 01/12] proxyRequest is now an override --- packages/open-next/src/adapters/middleware.ts | 20 +++-- .../open-next/src/build/createMiddleware.ts | 5 +- .../src/build/edge/createEdgeBundle.ts | 9 +- .../open-next/src/core/createMainHandler.ts | 5 ++ packages/open-next/src/core/requestHandler.ts | 29 +++---- packages/open-next/src/core/resolve.ts | 17 ++++ packages/open-next/src/core/routing/util.ts | 82 ------------------ .../overrides/converters/aws-cloudfront.ts | 22 +---- .../overrides/proxyExternalRequest/dummy.ts | 10 +++ .../overrides/proxyExternalRequest/fetch.ts | 28 +++++++ .../overrides/proxyExternalRequest/node.ts | 83 +++++++++++++++++++ packages/open-next/src/plugins/resolve.ts | 13 ++- packages/open-next/src/types/global.ts | 14 +++- packages/open-next/src/types/open-next.ts | 11 +++ packages/open-next/src/types/overrides.ts | 4 + 15 files changed, 222 insertions(+), 130 deletions(-) create mode 100644 packages/open-next/src/overrides/proxyExternalRequest/dummy.ts create mode 100644 packages/open-next/src/overrides/proxyExternalRequest/fetch.ts create mode 100644 packages/open-next/src/overrides/proxyExternalRequest/node.ts diff --git a/packages/open-next/src/adapters/middleware.ts b/packages/open-next/src/adapters/middleware.ts index 5ae66951..634f581c 100644 --- a/packages/open-next/src/adapters/middleware.ts +++ b/packages/open-next/src/adapters/middleware.ts @@ -6,6 +6,7 @@ import { createGenericHandler } from "../core/createGenericHandler"; import { resolveIncrementalCache, resolveOriginResolver, + resolveProxyRequest, resolveQueue, resolveTagCache, } from "../core/resolve"; @@ -19,6 +20,10 @@ const defaultHandler = async (internalEvent: InternalEvent) => { globalThis.openNextConfig.middleware?.originResolver, ); + const externalRequestProxy = await resolveProxyRequest( + globalThis.openNextConfig.middleware?.override?.proxyExternalRequest, + ); + //#override includeCacheInMiddleware globalThis.tagCache = await resolveTagCache( globalThis.openNextConfig.middleware?.override?.tagCache, @@ -43,14 +48,15 @@ const defaultHandler = async (internalEvent: InternalEvent) => { let origin: Origin | false = false; if (!result.isExternalRewrite) { origin = await originResolver.resolve(result.internalEvent.rawPath); + return { + type: "middleware", + internalEvent: result.internalEvent, + isExternalRewrite: result.isExternalRewrite, + origin, + isISR: result.isISR, + }; } - return { - type: "middleware", - internalEvent: result.internalEvent, - isExternalRewrite: result.isExternalRewrite, - origin, - isISR: result.isISR, - }; + return externalRequestProxy.proxy(result.internalEvent); } debug("Middleware response", result); diff --git a/packages/open-next/src/build/createMiddleware.ts b/packages/open-next/src/build/createMiddleware.ts index f74b8ac2..13a957ce 100644 --- a/packages/open-next/src/build/createMiddleware.ts +++ b/packages/open-next/src/build/createMiddleware.ts @@ -57,7 +57,10 @@ export async function createMiddleware( outfile: path.join(outputPath, "handler.mjs"), middlewareInfo, options, - overrides: config.middleware?.override, + overrides: { + ...config.middleware.override, + originResolver: config.middleware.originResolver, + }, defaultConverter: "aws-cloudfront", includeCache: config.dangerous?.enableCacheInterception, additionalExternals: config.edgeExternals, diff --git a/packages/open-next/src/build/edge/createEdgeBundle.ts b/packages/open-next/src/build/edge/createEdgeBundle.ts index 66a796db..d2bbd86a 100644 --- a/packages/open-next/src/build/edge/createEdgeBundle.ts +++ b/packages/open-next/src/build/edge/createEdgeBundle.ts @@ -6,11 +6,14 @@ import { build } from "esbuild"; import type { MiddlewareInfo, MiddlewareManifest } from "types/next-types"; import type { IncludedConverter, + IncludedOriginResolver, + LazyLoadedOverride, OverrideOptions, RouteTemplate, SplittedFunctionOptions, } from "types/open-next"; +import type { OriginResolver } from "types/overrides.js"; import logger from "../../logger.js"; import { openNextEdgePlugins } from "../../plugins/edge.js"; import { openNextReplacementPlugin } from "../../plugins/replacement.js"; @@ -23,7 +26,11 @@ interface BuildEdgeBundleOptions { entrypoint: string; outfile: string; options: BuildOptions; - overrides?: OverrideOptions; + overrides?: OverrideOptions & { + originResolver?: + | LazyLoadedOverride + | IncludedOriginResolver; + }; defaultConverter?: IncludedConverter; additionalInject?: string; includeCache?: boolean; diff --git a/packages/open-next/src/core/createMainHandler.ts b/packages/open-next/src/core/createMainHandler.ts index 3c96d7f7..72d26f47 100644 --- a/packages/open-next/src/core/createMainHandler.ts +++ b/packages/open-next/src/core/createMainHandler.ts @@ -6,6 +6,7 @@ import { openNextHandler } from "./requestHandler"; import { resolveConverter, resolveIncrementalCache, + resolveProxyRequest, resolveQueue, resolveTagCache, resolveWrapper, @@ -33,6 +34,10 @@ export async function createMainHandler() { globalThis.tagCache = await resolveTagCache(thisFunction.override?.tagCache); + globalThis.proxyExternalRequest = await resolveProxyRequest( + thisFunction.override?.proxyExternalRequest, + ); + globalThis.lastModified = {}; // From the config, we create the converter diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index ffce1038..0b5f5750 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -7,7 +7,8 @@ import { runWithOpenNextRequestContext } from "utils/promise"; import { debug, error, warn } from "../adapters/logger"; import { patchAsyncStorage } from "./patchAsyncStorage"; -import { convertRes, createServerResponse, proxyRequest } from "./routing/util"; +import { resolveProxyRequest } from "./resolve"; +import { convertRes, createServerResponse } from "./routing/util"; import type { MiddlewareOutputEvent } from "./routingHandler"; import routingHandler, { MIDDLEWARE_HEADER_PREFIX, @@ -65,6 +66,16 @@ export async function openNextHandler( delete headers[rawKey]; } + if ( + "isExternalRewrite" in preprocessResult && + preprocessResult.isExternalRewrite + ) { + const proxyResult = await globalThis.proxyExternalRequest.proxy( + preprocessResult.internalEvent, + ); + preprocessResult = proxyResult; + } + if ("type" in preprocessResult) { // response is used only in the streaming case if (responseStreaming) { @@ -110,7 +121,6 @@ export async function openNextHandler( store.mergeHeadersPriority = mergeHeadersPriority; } - const preprocessedResult = preprocessResult as MiddlewareOutputEvent; const req = new IncomingMessage(reqProps); const res = createServerResponse( preprocessedEvent, @@ -118,12 +128,7 @@ export async function openNextHandler( responseStreaming, ); - await processRequest( - req, - res, - preprocessedEvent, - preprocessedResult.isExternalRewrite, - ); + await processRequest(req, res, preprocessedEvent); const { statusCode, @@ -155,7 +160,6 @@ async function processRequest( req: IncomingMessage, res: OpenNextNodeResponse, internalEvent: InternalEvent, - isExternalRewrite?: boolean, ) { // @ts-ignore // Next.js doesn't parse body if the property exists @@ -163,13 +167,6 @@ async function processRequest( delete req.body; try { - // `serverHandler` is replaced at build time depending on user's - // nextjs version to patch Nextjs 13.4.x and future breaking changes. - - if (isExternalRewrite) { - return proxyRequest(internalEvent, res); - } - //#override applyNextjsPrebundledReact setNextjsPrebundledReact(internalEvent.rawPath); //#endOverride diff --git a/packages/open-next/src/core/resolve.ts b/packages/open-next/src/core/resolve.ts index d31edfc3..e6cede42 100644 --- a/packages/open-next/src/core/resolve.ts +++ b/packages/open-next/src/core/resolve.ts @@ -1,6 +1,7 @@ import type { BaseEventOrResult, DefaultOverrideOptions, + IncludedProxyExternalRequest, InternalEvent, InternalResult, LazyLoadedOverride, @@ -10,6 +11,7 @@ import type { Converter, ImageLoader, OriginResolver, + ProxyExternalRequest, TagCache, Warmer, Wrapper, @@ -130,3 +132,18 @@ export async function resolveWarmerInvoke( const m_1 = await import("../overrides/warmer/aws-lambda.js"); return m_1.default; } + +/** + * @__PURE__ + */ +export async function resolveProxyRequest( + proxyRequest?: + | LazyLoadedOverride + | IncludedProxyExternalRequest, +) { + if (typeof proxyRequest === "function") { + return proxyRequest(); + } + const m_1 = await import("../overrides/proxyExternalRequest/node.js"); + return m_1.default; +} diff --git a/packages/open-next/src/core/routing/util.ts b/packages/open-next/src/core/routing/util.ts index db5a9ba5..cba325f1 100644 --- a/packages/open-next/src/core/routing/util.ts +++ b/packages/open-next/src/core/routing/util.ts @@ -164,33 +164,6 @@ export function unescapeRegex(str: string) { .replaceAll("_ยต3_", "(...)"); } -/** - * - * @__PURE__ - */ -function filterHeadersForProxy( - headers: Record, -) { - const filteredHeaders: Record = {}; - const disallowedHeaders = [ - "host", - "connection", - "via", - "x-cache", - "transfer-encoding", - "content-encoding", - "content-length", - ]; - Object.entries(headers).forEach(([key, value]) => { - const lowerKey = key.toLowerCase(); - if (disallowedHeaders.includes(lowerKey) || lowerKey.startsWith("x-amz")) - return; - - filteredHeaders[key] = value?.toString() ?? ""; - }); - return filteredHeaders; -} - /** * @__PURE__ */ @@ -209,61 +182,6 @@ export function convertBodyToReadableStream( return readable; } -/** - * - * @__PURE__ - */ -export async function proxyRequest( - internalEvent: InternalEvent, - res: OpenNextNodeResponse, -) { - const { url, headers, method, body } = internalEvent; - const request = await import("node:https").then((m) => m.request); - debug("proxyRequest", url); - await new Promise((resolve, reject) => { - const filteredHeaders = filterHeadersForProxy(headers); - debug("filteredHeaders", filteredHeaders); - const req = request( - url, - { - headers: filteredHeaders, - method, - rejectUnauthorized: false, - }, - (_res) => { - res.writeHead( - _res.statusCode ?? 200, - filterHeadersForProxy(_res.headers), - ); - if (_res.headers["content-encoding"] === "br") { - _res.pipe(require("node:zlib").createBrotliDecompress()).pipe(res); - } else if (_res.headers["content-encoding"] === "gzip") { - _res.pipe(require("node:zlib").createGunzip()).pipe(res); - } else { - _res.pipe(res); - } - - _res.on("error", (e) => { - error("proxyRequest error", e); - res.end(); - reject(e); - }); - res.on("finish", () => { - resolve(); - }); - }, - ); - - if (body && method !== "GET" && method !== "HEAD") { - req.write(body); - } - req.end(); - }); - // console.log("result", result); - // res.writeHead(result.status, resHeaders); - // res.end(await result.text()); -} - enum CommonHeaders { CACHE_CONTROL = "cache-control", NEXT_CACHE = "x-nextjs-cache", diff --git a/packages/open-next/src/overrides/converters/aws-cloudfront.ts b/packages/open-next/src/overrides/converters/aws-cloudfront.ts index 3d60e53f..494cd84d 100644 --- a/packages/open-next/src/overrides/converters/aws-cloudfront.ts +++ b/packages/open-next/src/overrides/converters/aws-cloudfront.ts @@ -18,7 +18,6 @@ import { convertToQuery, convertToQueryString, createServerResponse, - proxyRequest, } from "../../core/routing/util"; import type { MiddlewareOutputEvent } from "../../core/routingHandler"; @@ -159,26 +158,7 @@ async function convertToCloudFrontRequestResult( const responseHeaders = result.internalEvent.headers; // Handle external rewrite - if (result.isExternalRewrite) { - const serverResponse = createServerResponse(result.internalEvent, {}); - await proxyRequest(result.internalEvent, serverResponse); - const externalResult = convertRes(serverResponse); - const body = await fromReadableStream( - externalResult.body, - externalResult.isBase64Encoded, - ); - const cloudfrontResult = { - status: externalResult.statusCode.toString(), - statusDescription: "OK", - headers: convertToCloudfrontHeaders(externalResult.headers, true), - bodyEncoding: externalResult.isBase64Encoded - ? ("base64" as const) - : ("text" as const), - body, - }; - debug("externalResult", cloudfrontResult); - return cloudfrontResult; - } + let customOrigin = origin?.custom as CloudFrontCustomOrigin; let host = responseHeaders.host ?? responseHeaders.Host; if (result.origin) { diff --git a/packages/open-next/src/overrides/proxyExternalRequest/dummy.ts b/packages/open-next/src/overrides/proxyExternalRequest/dummy.ts new file mode 100644 index 00000000..0ef1b51d --- /dev/null +++ b/packages/open-next/src/overrides/proxyExternalRequest/dummy.ts @@ -0,0 +1,10 @@ +import type { ProxyExternalRequest } from "types/overrides"; + +const DummyProxyExternalRequest: ProxyExternalRequest = { + name: "dummy", + proxy: async (_event) => { + throw new Error("This is a dummy implementation"); + }, +}; + +export default DummyProxyExternalRequest; diff --git a/packages/open-next/src/overrides/proxyExternalRequest/fetch.ts b/packages/open-next/src/overrides/proxyExternalRequest/fetch.ts new file mode 100644 index 00000000..c10234da --- /dev/null +++ b/packages/open-next/src/overrides/proxyExternalRequest/fetch.ts @@ -0,0 +1,28 @@ +import type { ProxyExternalRequest } from "types/overrides"; +import { emptyReadableStream } from "utils/stream"; + +const fetchProxy: ProxyExternalRequest = { + name: "fetch-proxy", + // @ts-ignore + proxy: async (internalEvent) => { + const { url, headers, method, body } = internalEvent; + const response = await fetch(url, { + method, + headers, + body, + }); + const responseHeaders: Record = {}; + response.headers.forEach((value, key) => { + responseHeaders[key] = value; + }); + return { + type: "core", + headers: responseHeaders, + statusCode: response.status, + isBase64Encoded: true, + body: response.body ?? emptyReadableStream(), + }; + }, +}; + +export default fetchProxy; diff --git a/packages/open-next/src/overrides/proxyExternalRequest/node.ts b/packages/open-next/src/overrides/proxyExternalRequest/node.ts new file mode 100644 index 00000000..143b7600 --- /dev/null +++ b/packages/open-next/src/overrides/proxyExternalRequest/node.ts @@ -0,0 +1,83 @@ +import { debug, error } from "node:console"; +import { request } from "node:https"; +import { Readable } from "node:stream"; +import type { InternalEvent, InternalResult } from "types/open-next"; +import type { ProxyExternalRequest } from "types/overrides"; +import { isBinaryContentType } from "../../adapters/binary"; + +function filterHeadersForProxy( + headers: Record, +) { + const filteredHeaders: Record = {}; + const disallowedHeaders = [ + "host", + "connection", + "via", + "x-cache", + "transfer-encoding", + "content-encoding", + "content-length", + ]; + Object.entries(headers).forEach(([key, value]) => { + const lowerKey = key.toLowerCase(); + if (disallowedHeaders.includes(lowerKey) || lowerKey.startsWith("x-amz")) + return; + + filteredHeaders[key] = value?.toString() ?? ""; + }); + return filteredHeaders; +} + +const nodeProxy: ProxyExternalRequest = { + name: "node-proxy", + proxy: (internalEvent: InternalEvent) => { + const { url, headers, method, body } = internalEvent; + debug("proxyRequest", url); + return new Promise((resolve, reject) => { + const filteredHeaders = filterHeadersForProxy(headers); + debug("filteredHeaders", filteredHeaders); + const req = request( + url, + { + headers: filteredHeaders, + method, + rejectUnauthorized: false, + }, + (_res) => { + const nodeReadableStream = + _res.headers["content-encoding"] === "br" + ? _res.pipe(require("node:zlib").createBrotliDecompress()) + : _res.headers["content-encoding"] === "gzip" + ? _res.pipe(require("node:zlib").createGunzip()) + : _res; + + const isBase64Encoded = + isBinaryContentType(headers["content-type"]) || + !!headers["content-encoding"]; + const result: InternalResult = { + type: "core", + headers: filterHeadersForProxy(_res.headers), + statusCode: _res.statusCode ?? 200, + // TODO: check base64 encoding + isBase64Encoded, + body: Readable.toWeb(nodeReadableStream), + }; + + resolve(result); + + _res.on("error", (e) => { + error("proxyRequest error", e); + reject(e); + }); + }, + ); + + if (body && method !== "GET" && method !== "HEAD") { + req.write(body); + } + req.end(); + }); + }, +}; + +export default nodeProxy; diff --git a/packages/open-next/src/plugins/resolve.ts b/packages/open-next/src/plugins/resolve.ts index a0ea01cc..44cf909f 100644 --- a/packages/open-next/src/plugins/resolve.ts +++ b/packages/open-next/src/plugins/resolve.ts @@ -6,11 +6,17 @@ import type { DefaultOverrideOptions, IncludedImageLoader, IncludedOriginResolver, + IncludedProxyExternalRequest, IncludedWarmer, LazyLoadedOverride, OverrideOptions, } from "types/open-next"; -import type { ImageLoader, OriginResolver, Warmer } from "types/overrides"; +import type { + ImageLoader, + OriginResolver, + ProxyExternalRequest, + Warmer, +} from "types/overrides"; import logger from "../logger.js"; @@ -26,6 +32,9 @@ export interface IPluginSettings { | LazyLoadedOverride | IncludedOriginResolver; warmer?: LazyLoadedOverride | IncludedWarmer; + proxyExternalRequest?: + | LazyLoadedOverride + | IncludedProxyExternalRequest; }; fnName?: string; } @@ -50,6 +59,7 @@ const nameToFolder = { imageLoader: "imageLoader", originResolver: "originResolver", warmer: "warmer", + proxyExternalRequest: "proxyExternalRequest", }; const defaultOverrides = { @@ -61,6 +71,7 @@ const defaultOverrides = { imageLoader: "s3", originResolver: "pattern-env", warmer: "aws-lambda", + proxyExternalRequest: "node", }; /** diff --git a/packages/open-next/src/types/global.ts b/packages/open-next/src/types/global.ts index 2f9191a3..033802a5 100644 --- a/packages/open-next/src/types/global.ts +++ b/packages/open-next/src/types/global.ts @@ -1,7 +1,12 @@ import type { AsyncLocalStorage } from "node:async_hooks"; import type { OutgoingHttpHeaders } from "node:http"; -import type { IncrementalCache, Queue, TagCache } from "types/overrides"; +import type { + IncrementalCache, + ProxyExternalRequest, + Queue, + TagCache, +} from "types/overrides"; import type { DetachedPromiseRunner } from "../utils/promise"; import type { OpenNextConfig } from "./open-next"; @@ -194,4 +199,11 @@ declare global { * Defined in `createMainHandler` or in `adapter/middleware.ts`. */ var queue: Queue; + + /** + * The function that is used when resolving external rewrite requests. + * Only available in main functions + * Defined in `createMainHandler`. + */ + var proxyExternalRequest: ProxyExternalRequest; } diff --git a/packages/open-next/src/types/open-next.ts b/packages/open-next/src/types/open-next.ts index bd975584..bfa16804 100644 --- a/packages/open-next/src/types/open-next.ts +++ b/packages/open-next/src/types/open-next.ts @@ -6,6 +6,7 @@ import type { ImageLoader, IncrementalCache, OriginResolver, + ProxyExternalRequest, Queue, TagCache, Warmer, @@ -102,6 +103,8 @@ export type IncludedOriginResolver = "pattern-env"; export type IncludedWarmer = "aws-lambda"; +export type IncludedProxyExternalRequest = "node" | "fetch"; + export interface DefaultOverrideOptions< E extends BaseEventOrResult = InternalEvent, R extends BaseEventOrResult = InternalResult, @@ -145,6 +148,14 @@ export interface OverrideOptions extends DefaultOverrideOptions { * @default "sqs" */ queue?: IncludedQueue | LazyLoadedOverride; + + /** + * Add possibility to override the default proxy for external rewrite + * @default "node" + */ + proxyExternalRequest?: + | IncludedProxyExternalRequest + | LazyLoadedOverride; } export interface InstallOptions { diff --git a/packages/open-next/src/types/overrides.ts b/packages/open-next/src/types/overrides.ts index 74bc171d..b245b7b2 100644 --- a/packages/open-next/src/types/overrides.ts +++ b/packages/open-next/src/types/overrides.ts @@ -135,3 +135,7 @@ export type ImageLoader = BaseOverride & { export type OriginResolver = BaseOverride & { resolve: (path: string) => Promise; }; + +export type ProxyExternalRequest = BaseOverride & { + proxy: (event: InternalEvent) => Promise; +}; From 055d1a8f5b21077306f05e574be68240bb6948e5 Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Thu, 14 Nov 2024 13:01:01 +0100 Subject: [PATCH 02/12] handle errors in request handler --- packages/open-next/src/core/requestHandler.ts | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index 0b5f5750..aaacb2bd 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -70,10 +70,29 @@ export async function openNextHandler( "isExternalRewrite" in preprocessResult && preprocessResult.isExternalRewrite ) { - const proxyResult = await globalThis.proxyExternalRequest.proxy( - preprocessResult.internalEvent, - ); - preprocessResult = proxyResult; + try { + const proxyResult = await globalThis.proxyExternalRequest.proxy( + preprocessResult.internalEvent, + ); + preprocessResult = proxyResult; + } catch (e) { + error("External request failed.", e); + preprocessResult = { + internalEvent: { + type: "core", + rawPath: "/500", + method: "GET", + headers: {}, + url: "/500", + query: {}, + cookies: {}, + remoteAddress: "", + }, + isExternalRewrite: false, + isISR: false, + origin: false, + }; + } } if ("type" in preprocessResult) { From e744a790b7f5b839a5a2b5a956bf78d0d40853e5 Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Fri, 15 Nov 2024 15:45:05 +0100 Subject: [PATCH 03/12] review fix --- packages/open-next/src/adapters/middleware.ts | 5 +++-- packages/open-next/src/core/requestHandler.ts | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/open-next/src/adapters/middleware.ts b/packages/open-next/src/adapters/middleware.ts index 634f581c..e65298f0 100644 --- a/packages/open-next/src/adapters/middleware.ts +++ b/packages/open-next/src/adapters/middleware.ts @@ -45,9 +45,10 @@ const defaultHandler = async (internalEvent: InternalEvent) => { const result = await routingHandler(internalEvent); if ("internalEvent" in result) { debug("Middleware intercepted event", internalEvent); - let origin: Origin | false = false; if (!result.isExternalRewrite) { - origin = await originResolver.resolve(result.internalEvent.rawPath); + const origin = await originResolver.resolve( + result.internalEvent.rawPath, + ); return { type: "middleware", internalEvent: result.internalEvent, diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index aaacb2bd..08a7d631 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -71,10 +71,9 @@ export async function openNextHandler( preprocessResult.isExternalRewrite ) { try { - const proxyResult = await globalThis.proxyExternalRequest.proxy( + preprocessResult = await globalThis.proxyExternalRequest.proxy( preprocessResult.internalEvent, ); - preprocessResult = proxyResult; } catch (e) { error("External request failed.", e); preprocessResult = { From 143f1c463d336a232ff598f063271a34de660628 Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Fri, 15 Nov 2024 15:50:00 +0100 Subject: [PATCH 04/12] add dummy type everywhere --- packages/open-next/src/build/validateConfig.ts | 1 + packages/open-next/src/core/resolve.ts | 3 ++- packages/open-next/src/types/open-next.ts | 17 +++++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/open-next/src/build/validateConfig.ts b/packages/open-next/src/build/validateConfig.ts index e7f84728..451f116b 100644 --- a/packages/open-next/src/build/validateConfig.ts +++ b/packages/open-next/src/build/validateConfig.ts @@ -18,6 +18,7 @@ const compatibilityMatrix: Record = { "aws-lambda-streaming": ["aws-apigw-v2"], cloudflare: ["edge"], node: ["node"], + dummy: [], }; function validateFunctionOptions(fnOptions: FunctionOptions) { diff --git a/packages/open-next/src/core/resolve.ts b/packages/open-next/src/core/resolve.ts index e6cede42..c63f32fa 100644 --- a/packages/open-next/src/core/resolve.ts +++ b/packages/open-next/src/core/resolve.ts @@ -2,6 +2,7 @@ import type { BaseEventOrResult, DefaultOverrideOptions, IncludedProxyExternalRequest, + IncludedWarmer, InternalEvent, InternalResult, LazyLoadedOverride, @@ -124,7 +125,7 @@ export async function resolveOriginResolver( * @__PURE__ */ export async function resolveWarmerInvoke( - warmer?: LazyLoadedOverride | "aws-lambda", + warmer?: LazyLoadedOverride | IncludedWarmer, ) { if (typeof warmer === "function") { return warmer(); diff --git a/packages/open-next/src/types/open-next.ts b/packages/open-next/src/types/open-next.ts index bfa16804..7a1ed705 100644 --- a/packages/open-next/src/types/open-next.ts +++ b/packages/open-next/src/types/open-next.ts @@ -80,7 +80,8 @@ export type IncludedWrapper = | "aws-lambda" | "aws-lambda-streaming" | "node" - | "cloudflare"; + | "cloudflare" + | "dummy"; export type IncludedConverter = | "aws-apigw-v2" @@ -91,19 +92,19 @@ export type IncludedConverter = | "sqs-revalidate" | "dummy"; -export type IncludedQueue = "sqs" | "sqs-lite"; +export type IncludedQueue = "sqs" | "sqs-lite" | "dummy"; -export type IncludedIncrementalCache = "s3" | "s3-lite"; +export type IncludedIncrementalCache = "s3" | "s3-lite" | "dummy"; -export type IncludedTagCache = "dynamodb" | "dynamodb-lite"; +export type IncludedTagCache = "dynamodb" | "dynamodb-lite" | "dummy"; -export type IncludedImageLoader = "s3" | "host"; +export type IncludedImageLoader = "s3" | "host" | "dummy"; -export type IncludedOriginResolver = "pattern-env"; +export type IncludedOriginResolver = "pattern-env" | "dummy"; -export type IncludedWarmer = "aws-lambda"; +export type IncludedWarmer = "aws-lambda" | "dummy"; -export type IncludedProxyExternalRequest = "node" | "fetch"; +export type IncludedProxyExternalRequest = "node" | "fetch" | "dummy"; export interface DefaultOverrideOptions< E extends BaseEventOrResult = InternalEvent, From ba535b7dda4d587d109e23953d33706c8ddad1ac Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Fri, 15 Nov 2024 15:54:24 +0100 Subject: [PATCH 05/12] handle error in middleware --- packages/open-next/src/adapters/middleware.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/open-next/src/adapters/middleware.ts b/packages/open-next/src/adapters/middleware.ts index e65298f0..d21eb1c9 100644 --- a/packages/open-next/src/adapters/middleware.ts +++ b/packages/open-next/src/adapters/middleware.ts @@ -1,7 +1,7 @@ import type { InternalEvent, Origin } from "types/open-next"; import { runWithOpenNextRequestContext } from "utils/promise"; -import { debug } from "../adapters/logger"; +import { debug, error } from "../adapters/logger"; import { createGenericHandler } from "../core/createGenericHandler"; import { resolveIncrementalCache, @@ -57,7 +57,23 @@ const defaultHandler = async (internalEvent: InternalEvent) => { isISR: result.isISR, }; } - return externalRequestProxy.proxy(result.internalEvent); + try { + return externalRequestProxy.proxy(result.internalEvent); + } catch (e) { + error("External request failed.", e); + return { + type: "middleware", + internalEvent: { + ...result.internalEvent, + rawPath: "/500", + url: "/500", + method: "GET", + }, + isExternalRewrite: result.isExternalRewrite, + origin: false, + isISR: result.isISR, + }; + } } debug("Middleware response", result); From 2c73d9a6ae09a8e8a2df12a0e94ef9ace49ef0cd Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Fri, 15 Nov 2024 16:54:14 +0100 Subject: [PATCH 06/12] review fix --- .../src/build/edge/createEdgeBundle.ts | 4 +++ packages/open-next/src/core/resolve.ts | 29 +++++++------------ packages/open-next/src/types/open-next.ts | 2 +- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/packages/open-next/src/build/edge/createEdgeBundle.ts b/packages/open-next/src/build/edge/createEdgeBundle.ts index d2bbd86a..9ec913c1 100644 --- a/packages/open-next/src/build/edge/createEdgeBundle.ts +++ b/packages/open-next/src/build/edge/createEdgeBundle.ts @@ -91,6 +91,10 @@ export async function buildEdgeBundle({ : "sqs-lite", } : {}), + originResolver: + typeof overrides?.originResolver === "string" + ? overrides.originResolver + : "pattern-env", }, fnName: name, }), diff --git a/packages/open-next/src/core/resolve.ts b/packages/open-next/src/core/resolve.ts index c63f32fa..21970e9e 100644 --- a/packages/open-next/src/core/resolve.ts +++ b/packages/open-next/src/core/resolve.ts @@ -1,22 +1,15 @@ import type { BaseEventOrResult, DefaultOverrideOptions, - IncludedProxyExternalRequest, - IncludedWarmer, InternalEvent, InternalResult, - LazyLoadedOverride, + OpenNextConfig, OverrideOptions, } from "types/open-next"; -import type { - Converter, - ImageLoader, - OriginResolver, - ProxyExternalRequest, - TagCache, - Warmer, - Wrapper, -} from "types/overrides"; +import type { Converter, TagCache, Wrapper } from "types/overrides"; + +// Just a little utility type to remove undefined from a type +type RemoveUndefined = T extends undefined ? never : T; export async function resolveConverter< E extends BaseEventOrResult = InternalEvent, @@ -98,7 +91,7 @@ export async function resolveIncrementalCache( * @__PURE__ */ export async function resolveImageLoader( - imageLoader: LazyLoadedOverride | string, + imageLoader: RemoveUndefined["loader"], ) { if (typeof imageLoader === "function") { return imageLoader(); @@ -112,7 +105,9 @@ export async function resolveImageLoader( * @__PURE__ */ export async function resolveOriginResolver( - originResolver?: LazyLoadedOverride | string, + originResolver: RemoveUndefined< + OpenNextConfig["middleware"] + >["originResolver"], ) { if (typeof originResolver === "function") { return originResolver(); @@ -125,7 +120,7 @@ export async function resolveOriginResolver( * @__PURE__ */ export async function resolveWarmerInvoke( - warmer?: LazyLoadedOverride | IncludedWarmer, + warmer: RemoveUndefined["invokeFunction"], ) { if (typeof warmer === "function") { return warmer(); @@ -138,9 +133,7 @@ export async function resolveWarmerInvoke( * @__PURE__ */ export async function resolveProxyRequest( - proxyRequest?: - | LazyLoadedOverride - | IncludedProxyExternalRequest, + proxyRequest: OverrideOptions["proxyExternalRequest"], ) { if (typeof proxyRequest === "function") { return proxyRequest(); diff --git a/packages/open-next/src/types/open-next.ts b/packages/open-next/src/types/open-next.ts index 7a1ed705..2138a381 100644 --- a/packages/open-next/src/types/open-next.ts +++ b/packages/open-next/src/types/open-next.ts @@ -309,7 +309,7 @@ export interface OpenNextConfig { * @default undefined */ warmer?: DefaultFunctionOptions & { - invokeFunction: IncludedWarmer | LazyLoadedOverride; + invokeFunction?: IncludedWarmer | LazyLoadedOverride; }; /** From 3b41f89229837e70063aee139992e8c02f9df810 Mon Sep 17 00:00:00 2001 From: conico974 Date: Mon, 18 Nov 2024 13:05:11 +0100 Subject: [PATCH 07/12] Update packages/open-next/src/core/requestHandler.ts Co-authored-by: Victor Berchet --- packages/open-next/src/core/requestHandler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index 08a7d631..519d28dd 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -67,8 +67,7 @@ export async function openNextHandler( } if ( - "isExternalRewrite" in preprocessResult && - preprocessResult.isExternalRewrite + preprocessResult.isExternalRewrite === true ) { try { preprocessResult = await globalThis.proxyExternalRequest.proxy( From b677414ce2a17123b1b753a039f5e9236051afdc Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Mon, 18 Nov 2024 13:11:54 +0100 Subject: [PATCH 08/12] review --- packages/open-next/src/core/requestHandler.ts | 1 + .../src/overrides/proxyExternalRequest/node.ts | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index 519d28dd..aba85dac 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -67,6 +67,7 @@ export async function openNextHandler( } if ( + "isExternalRewrite" in preprocessResult && preprocessResult.isExternalRewrite === true ) { try { diff --git a/packages/open-next/src/overrides/proxyExternalRequest/node.ts b/packages/open-next/src/overrides/proxyExternalRequest/node.ts index 143b7600..5ab0f465 100644 --- a/packages/open-next/src/overrides/proxyExternalRequest/node.ts +++ b/packages/open-next/src/overrides/proxyExternalRequest/node.ts @@ -18,13 +18,16 @@ function filterHeadersForProxy( "content-encoding", "content-length", ]; - Object.entries(headers).forEach(([key, value]) => { - const lowerKey = key.toLowerCase(); - if (disallowedHeaders.includes(lowerKey) || lowerKey.startsWith("x-amz")) - return; - - filteredHeaders[key] = value?.toString() ?? ""; - }); + Object.entries(headers) + .filter(([key, _]) => { + const lowerKey = key.toLowerCase(); + return !( + disallowedHeaders.includes(lowerKey) || lowerKey.startsWith("x-amz") + ); + }) + .forEach(([key, value]) => { + filteredHeaders[key] = value?.toString() ?? ""; + }); return filteredHeaders; } From d409b7d8f8862b3037921ed54d1c6a686d173177 Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Mon, 18 Nov 2024 13:49:14 +0100 Subject: [PATCH 09/12] added comment --- packages/open-next/src/adapters/middleware.ts | 3 ++- packages/open-next/src/core/requestHandler.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/open-next/src/adapters/middleware.ts b/packages/open-next/src/adapters/middleware.ts index d21eb1c9..952d58fa 100644 --- a/packages/open-next/src/adapters/middleware.ts +++ b/packages/open-next/src/adapters/middleware.ts @@ -69,7 +69,8 @@ const defaultHandler = async (internalEvent: InternalEvent) => { url: "/500", method: "GET", }, - isExternalRewrite: result.isExternalRewrite, + // On error we need to rewrite to the 500 page which is an internal rewrite + isExternalRewrite: false, origin: false, isISR: result.isISR, }; diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index aba85dac..1030581c 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -87,6 +87,7 @@ export async function openNextHandler( cookies: {}, remoteAddress: "", }, + // On error we need to rewrite to the 500 page which is an internal rewrite isExternalRewrite: false, isISR: false, origin: false, From fc0f0a4d6f36a877cfd2d795dd9227c1729f319f Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Mon, 18 Nov 2024 16:50:57 +0100 Subject: [PATCH 10/12] review fix --- .../src/build/edge/createEdgeBundle.ts | 4 ++++ packages/open-next/src/plugins/resolve.ts | 17 ++++------------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/open-next/src/build/edge/createEdgeBundle.ts b/packages/open-next/src/build/edge/createEdgeBundle.ts index 9ec913c1..ce8d785c 100644 --- a/packages/open-next/src/build/edge/createEdgeBundle.ts +++ b/packages/open-next/src/build/edge/createEdgeBundle.ts @@ -95,6 +95,10 @@ export async function buildEdgeBundle({ typeof overrides?.originResolver === "string" ? overrides.originResolver : "pattern-env", + proxyExternalRequest: + typeof overrides?.proxyExternalRequest === "string" + ? overrides.proxyExternalRequest + : "node", }, fnName: name, }), diff --git a/packages/open-next/src/plugins/resolve.ts b/packages/open-next/src/plugins/resolve.ts index 44cf909f..1d138dc9 100644 --- a/packages/open-next/src/plugins/resolve.ts +++ b/packages/open-next/src/plugins/resolve.ts @@ -3,27 +3,20 @@ import { readFileSync } from "node:fs"; import chalk from "chalk"; import type { Plugin } from "esbuild"; import type { - DefaultOverrideOptions, IncludedImageLoader, IncludedOriginResolver, - IncludedProxyExternalRequest, IncludedWarmer, LazyLoadedOverride, OverrideOptions, } from "types/open-next"; -import type { - ImageLoader, - OriginResolver, - ProxyExternalRequest, - Warmer, -} from "types/overrides"; +import type { ImageLoader, OriginResolver, Warmer } from "types/overrides"; import logger from "../logger.js"; export interface IPluginSettings { overrides?: { - wrapper?: DefaultOverrideOptions["wrapper"]; - converter?: DefaultOverrideOptions["converter"]; + wrapper?: OverrideOptions["wrapper"]; + converter?: OverrideOptions["converter"]; tagCache?: OverrideOptions["tagCache"]; queue?: OverrideOptions["queue"]; incrementalCache?: OverrideOptions["incrementalCache"]; @@ -32,9 +25,7 @@ export interface IPluginSettings { | LazyLoadedOverride | IncludedOriginResolver; warmer?: LazyLoadedOverride | IncludedWarmer; - proxyExternalRequest?: - | LazyLoadedOverride - | IncludedProxyExternalRequest; + proxyExternalRequest?: OverrideOptions["proxyExternalRequest"]; }; fnName?: string; } From b165231576e3d411517c710444118f4df243090f Mon Sep 17 00:00:00 2001 From: Dorseuil Nicolas Date: Mon, 18 Nov 2024 17:52:22 +0100 Subject: [PATCH 11/12] fix build --- packages/open-next/src/plugins/resolve.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/open-next/src/plugins/resolve.ts b/packages/open-next/src/plugins/resolve.ts index 1d138dc9..aa0b9d35 100644 --- a/packages/open-next/src/plugins/resolve.ts +++ b/packages/open-next/src/plugins/resolve.ts @@ -3,6 +3,7 @@ import { readFileSync } from "node:fs"; import chalk from "chalk"; import type { Plugin } from "esbuild"; import type { + DefaultOverrideOptions, IncludedImageLoader, IncludedOriginResolver, IncludedWarmer, @@ -15,8 +16,8 @@ import logger from "../logger.js"; export interface IPluginSettings { overrides?: { - wrapper?: OverrideOptions["wrapper"]; - converter?: OverrideOptions["converter"]; + wrapper?: DefaultOverrideOptions["wrapper"]; + converter?: DefaultOverrideOptions["converter"]; tagCache?: OverrideOptions["tagCache"]; queue?: OverrideOptions["queue"]; incrementalCache?: OverrideOptions["incrementalCache"]; From 8ea7d39af8bdb7aa8d62b46231d7e287b36c353b Mon Sep 17 00:00:00 2001 From: conico974 Date: Tue, 19 Nov 2024 14:29:49 +0100 Subject: [PATCH 12/12] Create strong-keys-ring.md --- .changeset/strong-keys-ring.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-keys-ring.md diff --git a/.changeset/strong-keys-ring.md b/.changeset/strong-keys-ring.md new file mode 100644 index 00000000..98b70d0c --- /dev/null +++ b/.changeset/strong-keys-ring.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/aws": patch +--- + +Feat: Allow overriding the proxying for external rewrite