Skip to content

Commit b999c4e

Browse files
conico974vicb
andauthored
Feat: Allow overriding the proxying for external rewrite (#630)
* proxyRequest is now an override * handle errors in request handler * review fix * add dummy type everywhere * handle error in middleware * review fix * Update packages/open-next/src/core/requestHandler.ts Co-authored-by: Victor Berchet <victor@suumit.com> * review * added comment * review fix * fix build * Create strong-keys-ring.md --------- Co-authored-by: Victor Berchet <victor@suumit.com>
1 parent ba84259 commit b999c4e

File tree

17 files changed

+285
-152
lines changed

17 files changed

+285
-152
lines changed

.changeset/strong-keys-ring.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@opennextjs/aws": patch
3+
---
4+
5+
Feat: Allow overriding the proxying for external rewrite

packages/open-next/src/adapters/middleware.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { InternalEvent, Origin } from "types/open-next";
22
import { runWithOpenNextRequestContext } from "utils/promise";
33

4-
import { debug } from "../adapters/logger";
4+
import { debug, error } from "../adapters/logger";
55
import { createGenericHandler } from "../core/createGenericHandler";
66
import {
77
resolveIncrementalCache,
88
resolveOriginResolver,
9+
resolveProxyRequest,
910
resolveQueue,
1011
resolveTagCache,
1112
} from "../core/resolve";
@@ -19,6 +20,10 @@ const defaultHandler = async (internalEvent: InternalEvent) => {
1920
globalThis.openNextConfig.middleware?.originResolver,
2021
);
2122

23+
const externalRequestProxy = await resolveProxyRequest(
24+
globalThis.openNextConfig.middleware?.override?.proxyExternalRequest,
25+
);
26+
2227
//#override includeCacheInMiddleware
2328
globalThis.tagCache = await resolveTagCache(
2429
globalThis.openNextConfig.middleware?.override?.tagCache,
@@ -40,17 +45,36 @@ const defaultHandler = async (internalEvent: InternalEvent) => {
4045
const result = await routingHandler(internalEvent);
4146
if ("internalEvent" in result) {
4247
debug("Middleware intercepted event", internalEvent);
43-
let origin: Origin | false = false;
4448
if (!result.isExternalRewrite) {
45-
origin = await originResolver.resolve(result.internalEvent.rawPath);
49+
const origin = await originResolver.resolve(
50+
result.internalEvent.rawPath,
51+
);
52+
return {
53+
type: "middleware",
54+
internalEvent: result.internalEvent,
55+
isExternalRewrite: result.isExternalRewrite,
56+
origin,
57+
isISR: result.isISR,
58+
};
59+
}
60+
try {
61+
return externalRequestProxy.proxy(result.internalEvent);
62+
} catch (e) {
63+
error("External request failed.", e);
64+
return {
65+
type: "middleware",
66+
internalEvent: {
67+
...result.internalEvent,
68+
rawPath: "/500",
69+
url: "/500",
70+
method: "GET",
71+
},
72+
// On error we need to rewrite to the 500 page which is an internal rewrite
73+
isExternalRewrite: false,
74+
origin: false,
75+
isISR: result.isISR,
76+
};
4677
}
47-
return {
48-
type: "middleware",
49-
internalEvent: result.internalEvent,
50-
isExternalRewrite: result.isExternalRewrite,
51-
origin,
52-
isISR: result.isISR,
53-
};
5478
}
5579

5680
debug("Middleware response", result);

packages/open-next/src/build/createMiddleware.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ export async function createMiddleware(
5757
outfile: path.join(outputPath, "handler.mjs"),
5858
middlewareInfo,
5959
options,
60-
overrides: config.middleware?.override,
60+
overrides: {
61+
...config.middleware.override,
62+
originResolver: config.middleware.originResolver,
63+
},
6164
defaultConverter: "aws-cloudfront",
6265
includeCache: config.dangerous?.enableCacheInterception,
6366
additionalExternals: config.edgeExternals,

packages/open-next/src/build/edge/createEdgeBundle.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import { build } from "esbuild";
66
import type { MiddlewareInfo, MiddlewareManifest } from "types/next-types";
77
import type {
88
IncludedConverter,
9+
IncludedOriginResolver,
10+
LazyLoadedOverride,
911
OverrideOptions,
1012
RouteTemplate,
1113
SplittedFunctionOptions,
1214
} from "types/open-next";
1315

16+
import type { OriginResolver } from "types/overrides.js";
1417
import logger from "../../logger.js";
1518
import { openNextEdgePlugins } from "../../plugins/edge.js";
1619
import { openNextReplacementPlugin } from "../../plugins/replacement.js";
@@ -23,7 +26,11 @@ interface BuildEdgeBundleOptions {
2326
entrypoint: string;
2427
outfile: string;
2528
options: BuildOptions;
26-
overrides?: OverrideOptions;
29+
overrides?: OverrideOptions & {
30+
originResolver?:
31+
| LazyLoadedOverride<OriginResolver>
32+
| IncludedOriginResolver;
33+
};
2734
defaultConverter?: IncludedConverter;
2835
additionalInject?: string;
2936
includeCache?: boolean;
@@ -84,6 +91,14 @@ export async function buildEdgeBundle({
8491
: "sqs-lite",
8592
}
8693
: {}),
94+
originResolver:
95+
typeof overrides?.originResolver === "string"
96+
? overrides.originResolver
97+
: "pattern-env",
98+
proxyExternalRequest:
99+
typeof overrides?.proxyExternalRequest === "string"
100+
? overrides.proxyExternalRequest
101+
: "node",
87102
},
88103
fnName: name,
89104
}),

packages/open-next/src/build/validateConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const compatibilityMatrix: Record<IncludedWrapper, IncludedConverter[]> = {
1818
"aws-lambda-streaming": ["aws-apigw-v2"],
1919
cloudflare: ["edge"],
2020
node: ["node"],
21+
dummy: [],
2122
};
2223

2324
function validateFunctionOptions(fnOptions: FunctionOptions) {

packages/open-next/src/core/createMainHandler.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { openNextHandler } from "./requestHandler";
66
import {
77
resolveConverter,
88
resolveIncrementalCache,
9+
resolveProxyRequest,
910
resolveQueue,
1011
resolveTagCache,
1112
resolveWrapper,
@@ -33,6 +34,10 @@ export async function createMainHandler() {
3334

3435
globalThis.tagCache = await resolveTagCache(thisFunction.override?.tagCache);
3536

37+
globalThis.proxyExternalRequest = await resolveProxyRequest(
38+
thisFunction.override?.proxyExternalRequest,
39+
);
40+
3641
globalThis.lastModified = {};
3742

3843
// From the config, we create the converter

packages/open-next/src/core/requestHandler.ts

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { runWithOpenNextRequestContext } from "utils/promise";
77

88
import { debug, error, warn } from "../adapters/logger";
99
import { patchAsyncStorage } from "./patchAsyncStorage";
10-
import { convertRes, createServerResponse, proxyRequest } from "./routing/util";
10+
import { resolveProxyRequest } from "./resolve";
11+
import { convertRes, createServerResponse } from "./routing/util";
1112
import type { MiddlewareOutputEvent } from "./routingHandler";
1213
import routingHandler, {
1314
MIDDLEWARE_HEADER_PREFIX,
@@ -65,6 +66,35 @@ export async function openNextHandler(
6566
delete headers[rawKey];
6667
}
6768

69+
if (
70+
"isExternalRewrite" in preprocessResult &&
71+
preprocessResult.isExternalRewrite === true
72+
) {
73+
try {
74+
preprocessResult = await globalThis.proxyExternalRequest.proxy(
75+
preprocessResult.internalEvent,
76+
);
77+
} catch (e) {
78+
error("External request failed.", e);
79+
preprocessResult = {
80+
internalEvent: {
81+
type: "core",
82+
rawPath: "/500",
83+
method: "GET",
84+
headers: {},
85+
url: "/500",
86+
query: {},
87+
cookies: {},
88+
remoteAddress: "",
89+
},
90+
// On error we need to rewrite to the 500 page which is an internal rewrite
91+
isExternalRewrite: false,
92+
isISR: false,
93+
origin: false,
94+
};
95+
}
96+
}
97+
6898
if ("type" in preprocessResult) {
6999
// response is used only in the streaming case
70100
if (responseStreaming) {
@@ -110,20 +140,14 @@ export async function openNextHandler(
110140
store.mergeHeadersPriority = mergeHeadersPriority;
111141
}
112142

113-
const preprocessedResult = preprocessResult as MiddlewareOutputEvent;
114143
const req = new IncomingMessage(reqProps);
115144
const res = createServerResponse(
116145
preprocessedEvent,
117146
overwrittenResponseHeaders,
118147
responseStreaming,
119148
);
120149

121-
await processRequest(
122-
req,
123-
res,
124-
preprocessedEvent,
125-
preprocessedResult.isExternalRewrite,
126-
);
150+
await processRequest(req, res, preprocessedEvent);
127151

128152
const {
129153
statusCode,
@@ -155,21 +179,13 @@ async function processRequest(
155179
req: IncomingMessage,
156180
res: OpenNextNodeResponse,
157181
internalEvent: InternalEvent,
158-
isExternalRewrite?: boolean,
159182
) {
160183
// @ts-ignore
161184
// Next.js doesn't parse body if the property exists
162185
// https://github.com/dougmoscrop/serverless-http/issues/227
163186
delete req.body;
164187

165188
try {
166-
// `serverHandler` is replaced at build time depending on user's
167-
// nextjs version to patch Nextjs 13.4.x and future breaking changes.
168-
169-
if (isExternalRewrite) {
170-
return proxyRequest(internalEvent, res);
171-
}
172-
173189
//#override applyNextjsPrebundledReact
174190
setNextjsPrebundledReact(internalEvent.rawPath);
175191
//#endOverride

packages/open-next/src/core/resolve.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,13 @@ import type {
33
DefaultOverrideOptions,
44
InternalEvent,
55
InternalResult,
6-
LazyLoadedOverride,
6+
OpenNextConfig,
77
OverrideOptions,
88
} from "types/open-next";
9-
import type {
10-
Converter,
11-
ImageLoader,
12-
OriginResolver,
13-
TagCache,
14-
Warmer,
15-
Wrapper,
16-
} from "types/overrides";
9+
import type { Converter, TagCache, Wrapper } from "types/overrides";
10+
11+
// Just a little utility type to remove undefined from a type
12+
type RemoveUndefined<T> = T extends undefined ? never : T;
1713

1814
export async function resolveConverter<
1915
E extends BaseEventOrResult = InternalEvent,
@@ -95,7 +91,7 @@ export async function resolveIncrementalCache(
9591
* @__PURE__
9692
*/
9793
export async function resolveImageLoader(
98-
imageLoader: LazyLoadedOverride<ImageLoader> | string,
94+
imageLoader: RemoveUndefined<OpenNextConfig["imageOptimization"]>["loader"],
9995
) {
10096
if (typeof imageLoader === "function") {
10197
return imageLoader();
@@ -109,7 +105,9 @@ export async function resolveImageLoader(
109105
* @__PURE__
110106
*/
111107
export async function resolveOriginResolver(
112-
originResolver?: LazyLoadedOverride<OriginResolver> | string,
108+
originResolver: RemoveUndefined<
109+
OpenNextConfig["middleware"]
110+
>["originResolver"],
113111
) {
114112
if (typeof originResolver === "function") {
115113
return originResolver();
@@ -122,11 +120,24 @@ export async function resolveOriginResolver(
122120
* @__PURE__
123121
*/
124122
export async function resolveWarmerInvoke(
125-
warmer?: LazyLoadedOverride<Warmer> | "aws-lambda",
123+
warmer: RemoveUndefined<OpenNextConfig["warmer"]>["invokeFunction"],
126124
) {
127125
if (typeof warmer === "function") {
128126
return warmer();
129127
}
130128
const m_1 = await import("../overrides/warmer/aws-lambda.js");
131129
return m_1.default;
132130
}
131+
132+
/**
133+
* @__PURE__
134+
*/
135+
export async function resolveProxyRequest(
136+
proxyRequest: OverrideOptions["proxyExternalRequest"],
137+
) {
138+
if (typeof proxyRequest === "function") {
139+
return proxyRequest();
140+
}
141+
const m_1 = await import("../overrides/proxyExternalRequest/node.js");
142+
return m_1.default;
143+
}

0 commit comments

Comments
 (0)