Skip to content

Commit 6b4c1e1

Browse files
committed
fixup! take 2
1 parent 848d6aa commit 6b4c1e1

File tree

7 files changed

+125
-87
lines changed

7 files changed

+125
-87
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { debug } from "../adapters/logger";
44
import { generateUniqueId } from "../adapters/util";
55
import { openNextHandler } from "./requestHandler";
66
import {
7+
resolveAssetResolver,
78
resolveCdnInvalidation,
89
resolveConverter,
910
resolveIncrementalCache,
@@ -38,6 +39,12 @@ export async function createMainHandler() {
3839

3940
globalThis.tagCache = await resolveTagCache(thisFunction.override?.tagCache);
4041

42+
if (config.middleware?.external !== true) {
43+
globalThis.assetResolver = await resolveAssetResolver(
44+
globalThis.openNextConfig.middleware?.assetResolver,
45+
);
46+
}
47+
4148
globalThis.proxyExternalRequest = await resolveProxyRequest(
4249
thisFunction.override?.proxyExternalRequest,
4350
);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ export async function openNextHandler(
8080
};
8181

8282
//#override withRouting
83-
routingResult = await routingHandler(internalEvent);
83+
routingResult = await routingHandler(internalEvent, {
84+
assetResolver: globalThis.assetResolver,
85+
});
8486
//#endOverride
8587

8688
const headers =

packages/open-next/src/core/routing/matcher.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ export function getNextConfigHeaders(
170170
return requestHeaders;
171171
}
172172

173+
/**
174+
* TODO: This method currently only check for the first match.
175+
* It should check for all matches for `beforeFiles` and `afterFiles` rewrite
176+
* See https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites
177+
*/
173178
export function handleRewrites<T extends RewriteDefinition>(
174179
event: InternalEvent,
175180
rewrites: T[],

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

Lines changed: 93 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,31 @@ const geoHeaderToNextHeader = {
4848
"x-open-next-longitude": "x-vercel-ip-longitude",
4949
};
5050

51+
/**
52+
* Adds the middleware headers to an event or result.
53+
*
54+
* @param eventOrResult
55+
* @param middlewareHeaders
56+
*/
5157
function applyMiddlewareHeaders(
52-
eventHeaders: Record<string, string | string[]>,
58+
eventOrResult: InternalEvent | InternalResult,
5359
middlewareHeaders: Record<string, string | string[] | undefined>,
54-
setPrefix = true,
5560
) {
56-
const keyPrefix = setPrefix ? MIDDLEWARE_HEADER_PREFIX : "";
61+
// Use the `MIDDLEWARE_HEADER_PREFIX` prefix for events, they will be processed by the request handler later.
62+
// Results do not go through the request handler and should not be prefixed.
63+
const isResult = isInternalResult(eventOrResult);
64+
const headers = eventOrResult.headers;
65+
const keyPrefix = isResult ? "" : MIDDLEWARE_HEADER_PREFIX;
5766
Object.entries(middlewareHeaders).forEach(([key, value]) => {
5867
if (value) {
59-
eventHeaders[keyPrefix + key] = Array.isArray(value)
60-
? value.join(",")
61-
: value;
68+
headers[keyPrefix + key] = Array.isArray(value) ? value.join(",") : value;
6269
}
6370
});
6471
}
6572

6673
export default async function routingHandler(
6774
event: InternalEvent,
68-
{ assetResolver }: { assetResolver?: AssetResolver } = {},
75+
{ assetResolver }: { assetResolver?: AssetResolver },
6976
): Promise<InternalResult | RoutingResult> {
7077
try {
7178
// Add Next geo headers
@@ -91,14 +98,13 @@ export default async function routingHandler(
9198

9299
const nextHeaders = getNextConfigHeaders(event, ConfigHeaders);
93100

94-
const internalEventOrResult = fixDataPage(event, BuildId);
95-
if ("statusCode" in internalEventOrResult) {
96-
return internalEventOrResult;
97-
}
101+
let eventOrResult = fixDataPage(event, BuildId);
98102

99-
let internalEvent: InternalEvent | InternalResult = internalEventOrResult;
103+
if (isInternalResult(eventOrResult)) {
104+
return eventOrResult;
105+
}
100106

101-
const redirect = handleRedirects(internalEvent, RoutesManifest.redirects);
107+
const redirect = handleRedirects(eventOrResult, RoutesManifest.redirects);
102108
if (redirect) {
103109
// We need to encode the value in the Location header to make sure it is valid according to RFC
104110
// https://stackoverflow.com/a/7654605/16587222
@@ -110,68 +116,87 @@ export default async function routingHandler(
110116
}
111117

112118
const middlewareEventOrResult = await handleMiddleware(
113-
internalEvent,
119+
eventOrResult,
114120
// We need to pass the initial search without any decoding
115121
// TODO: we'd need to refactor InternalEvent to include the initial querystring directly
116122
// Should be done in another PR because it is a breaking change
117123
new URL(event.url).search,
118124
);
119-
if ("statusCode" in middlewareEventOrResult) {
125+
if (isInternalResult(middlewareEventOrResult)) {
120126
return middlewareEventOrResult;
121127
}
122128

123-
const middlewareResponseHeaders = middlewareEventOrResult.responseHeaders;
129+
const middlewareResponseHeaders = {
130+
...middlewareEventOrResult.responseHeaders,
131+
...nextHeaders,
132+
};
124133
let isExternalRewrite = middlewareEventOrResult.isExternalRewrite ?? false;
125-
internalEvent = middlewareEventOrResult;
134+
eventOrResult = middlewareEventOrResult;
126135

127136
if (!isExternalRewrite) {
128137
// First rewrite to be applied
129-
const beforeRewrites = handleRewrites(
130-
internalEvent,
138+
const beforeRewrite = handleRewrites(
139+
eventOrResult,
131140
RoutesManifest.rewrites.beforeFiles,
132141
);
133-
internalEvent = beforeRewrites.internalEvent;
134-
isExternalRewrite = beforeRewrites.isExternalRewrite;
142+
eventOrResult = beforeRewrite.internalEvent;
143+
isExternalRewrite = beforeRewrite.isExternalRewrite;
144+
if (!isExternalRewrite) {
145+
const assetResult =
146+
await assetResolver?.getMaybeAssetResult?.(eventOrResult);
147+
if (assetResult) {
148+
applyMiddlewareHeaders(assetResult, middlewareResponseHeaders);
149+
return assetResult;
150+
}
151+
}
135152
}
136-
const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
153+
const foundStaticRoute = staticRouteMatcher(eventOrResult.rawPath);
137154
const isStaticRoute = !isExternalRewrite && foundStaticRoute.length > 0;
138155

139156
if (!(isStaticRoute || isExternalRewrite)) {
140157
// Second rewrite to be applied
141-
const afterRewrites = handleRewrites(
142-
internalEvent,
158+
const afterRewrite = handleRewrites(
159+
eventOrResult,
143160
RoutesManifest.rewrites.afterFiles,
144161
);
145-
internalEvent = afterRewrites.internalEvent;
146-
isExternalRewrite = afterRewrites.isExternalRewrite;
162+
eventOrResult = afterRewrite.internalEvent;
163+
isExternalRewrite = afterRewrite.isExternalRewrite;
164+
if (!isExternalRewrite) {
165+
const assetResult =
166+
await assetResolver?.getMaybeAssetResult?.(eventOrResult);
167+
if (assetResult) {
168+
applyMiddlewareHeaders(assetResult, middlewareResponseHeaders);
169+
return assetResult;
170+
}
171+
}
147172
}
148173

149174
let isISR = false;
150175
// We want to run this just before the dynamic route check
151176
// We can skip it if its an external rewrite
152177
if (!isExternalRewrite) {
153178
const fallbackResult = handleFallbackFalse(
154-
internalEvent,
179+
eventOrResult,
155180
PrerenderManifest,
156181
);
157-
internalEvent = fallbackResult.event;
182+
eventOrResult = fallbackResult.event;
158183
isISR = fallbackResult.isISR;
159184
}
160185

161-
const foundDynamicRoute = dynamicRouteMatcher(internalEvent.rawPath);
186+
const foundDynamicRoute = dynamicRouteMatcher(eventOrResult.rawPath);
162187
const isDynamicRoute = !isExternalRewrite && foundDynamicRoute.length > 0;
163188

164189
if (!(isDynamicRoute || isStaticRoute || isExternalRewrite)) {
165190
// Fallback rewrite to be applied
166191
const fallbackRewrites = handleRewrites(
167-
internalEvent,
192+
eventOrResult,
168193
RoutesManifest.rewrites.fallback,
169194
);
170-
internalEvent = fallbackRewrites.internalEvent;
195+
eventOrResult = fallbackRewrites.internalEvent;
171196
isExternalRewrite = fallbackRewrites.isExternalRewrite;
172197
}
173198

174-
const isNextImageRoute = internalEvent.rawPath.startsWith("/_next/image");
199+
const isNextImageRoute = eventOrResult.rawPath.startsWith("/_next/image");
175200

176201
const isRouteFoundBeforeAllRewrites =
177202
isStaticRoute || isDynamicRoute || isExternalRewrite;
@@ -183,31 +208,23 @@ export default async function routingHandler(
183208
isRouteFoundBeforeAllRewrites ||
184209
isNextImageRoute ||
185210
// We need to check again once all rewrites have been applied
186-
staticRouteMatcher(internalEvent.rawPath).length > 0 ||
187-
dynamicRouteMatcher(internalEvent.rawPath).length > 0
211+
staticRouteMatcher(eventOrResult.rawPath).length > 0 ||
212+
dynamicRouteMatcher(eventOrResult.rawPath).length > 0
188213
)
189214
) {
190-
const assetEventOrResult =
191-
await assetResolver?.onRouteNotFound(internalEvent);
192-
193-
if (assetEventOrResult && "statusCode" in assetEventOrResult) {
194-
applyMiddlewareHeaders(
195-
assetEventOrResult.headers,
196-
{
197-
...middlewareResponseHeaders,
198-
...nextHeaders,
199-
},
200-
false,
201-
);
202-
return assetEventOrResult;
215+
const assetResult =
216+
await assetResolver?.getMaybeAssetResult?.(eventOrResult);
217+
if (assetResult) {
218+
applyMiddlewareHeaders(assetResult, middlewareResponseHeaders);
219+
return assetResult;
203220
}
204221

205-
internalEvent = assetEventOrResult ?? {
206-
...internalEvent,
222+
eventOrResult = {
223+
...eventOrResult,
207224
rawPath: "/404",
208-
url: constructNextUrl(internalEvent.url, "/404"),
225+
url: constructNextUrl(eventOrResult.url, "/404"),
209226
headers: {
210-
...internalEvent.headers,
227+
...eventOrResult.headers,
211228
"x-middleware-response-cache-control":
212229
"private, no-cache, no-store, max-age=0, must-revalidate",
213230
},
@@ -216,28 +233,25 @@ export default async function routingHandler(
216233

217234
if (
218235
globalThis.openNextConfig.dangerous?.enableCacheInterception &&
219-
!("statusCode" in internalEvent)
236+
!isInternalResult(eventOrResult)
220237
) {
221238
debug("Cache interception enabled");
222-
internalEvent = await cacheInterceptor(internalEvent);
223-
if ("statusCode" in internalEvent) {
224-
applyMiddlewareHeaders(
225-
internalEvent.headers,
226-
{
227-
...middlewareResponseHeaders,
228-
...nextHeaders,
229-
},
230-
false,
231-
);
232-
return internalEvent;
239+
eventOrResult = await cacheInterceptor(eventOrResult);
240+
if (isInternalResult(eventOrResult)) {
241+
applyMiddlewareHeaders(eventOrResult, middlewareResponseHeaders);
242+
return eventOrResult;
233243
}
234244
}
235245

246+
const assetResult =
247+
await assetResolver?.getMaybeAssetResult?.(eventOrResult);
248+
if (assetResult) {
249+
applyMiddlewareHeaders(assetResult, middlewareResponseHeaders);
250+
return assetResult;
251+
}
252+
236253
// We apply the headers from the middleware response last
237-
applyMiddlewareHeaders(internalEvent.headers, {
238-
...middlewareResponseHeaders,
239-
...nextHeaders,
240-
});
254+
applyMiddlewareHeaders(eventOrResult, middlewareResponseHeaders);
241255

242256
const resolvedRoutes: ResolvedRoute[] = [
243257
...foundStaticRoute,
@@ -247,14 +261,14 @@ export default async function routingHandler(
247261
debug("resolvedRoutes", resolvedRoutes);
248262

249263
return {
250-
internalEvent,
264+
internalEvent: eventOrResult,
251265
isExternalRewrite,
252266
origin: false,
253267
isISR,
254268
resolvedRoutes,
255269
initialURL: event.url,
256270
locale: NextConfig.i18n
257-
? detectLocale(internalEvent, NextConfig.i18n)
271+
? detectLocale(eventOrResult, NextConfig.i18n)
258272
: undefined,
259273
};
260274
} catch (e) {
@@ -284,3 +298,13 @@ export default async function routingHandler(
284298
};
285299
}
286300
}
301+
302+
/**
303+
* @param eventOrResult
304+
* @returns Whether the event is an instance of `InternalResult`
305+
*/
306+
function isInternalResult(
307+
eventOrResult: InternalEvent | InternalResult,
308+
): eventOrResult is InternalResult {
309+
return eventOrResult != null && "statusCode" in eventOrResult;
310+
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import type { InternalEvent } from "types/open-next";
21
import type { AssetResolver } from "types/overrides";
32

3+
/**
4+
* A dummy asset resolver.
5+
*
6+
* It never overrides the result with an asset.
7+
*/
48
const resolver: AssetResolver = {
59
name: "dummy",
6-
// @returns `undefined` to preserve the routing layer default behavior (404 page)
7-
onRouteNotFound: (_event: InternalEvent) => undefined,
810
};
911

1012
export default resolver;

packages/open-next/src/types/global.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { AsyncLocalStorage } from "node:async_hooks";
22
import type { OutgoingHttpHeaders } from "node:http";
33

44
import type {
5+
AssetResolver,
56
CDNInvalidationHandler,
67
IncrementalCache,
78
ProxyExternalRequest,
@@ -214,6 +215,13 @@ declare global {
214215
*/
215216
var cdnInvalidationHandler: CDNInvalidationHandler;
216217

218+
/**
219+
* The function called to resolve assets.
220+
* Available in main functions
221+
* Defined in `createMainHandler` when the middleware is internal
222+
*/
223+
var assetResolver: AssetResolver | undefined;
224+
217225
/**
218226
* A function to preload the routes.
219227
* This needs to be defined on globalThis because it can be used by custom overrides.

packages/open-next/src/types/overrides.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,11 @@ export interface Queue {
3535
* Resolves assets in the routing layer.
3636
*/
3737
export interface AssetResolver {
38-
// TODO: add onCatchAllRoute, called when a catchAllRoute matches as it could potentially match an asset
39-
// onCatchAllRoute
40-
41-
/**
42-
* Called when no matching route is found.
43-
*
44-
* @param event The internal event from the routing layer
45-
* @returns - `Promise<InternalEvent>` replaces the internal event in the routing layer
46-
* - `Promise<InternalResult>` will get the routing layer to return the result, i.e. an asset
47-
* - `Promise<undefined> | undefined` does not change the routing default behavior of returning a 404
48-
*/
49-
onRouteNotFound(
50-
event: InternalEvent,
51-
): Promise<InternalEvent | InternalResult | undefined> | undefined;
5238
name: string;
39+
40+
getMaybeAssetResult?: (
41+
event: InternalEvent,
42+
) => Promise<InternalResult | undefined> | undefined;
5343
}
5444

5545
// Incremental cache

0 commit comments

Comments
 (0)