Skip to content

Commit e48951f

Browse files
authored
refactor: internalEvent.url is a full URL (#752)
1 parent 674dce6 commit e48951f

22 files changed

+184
-185
lines changed

.changeset/smooth-laws-pull.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@opennextjs/aws": patch
3+
---
4+
5+
`InternalEvent#url` is now a full URL
6+
7+
BREAKING CHANGE: `InternalEvent#url` was only composed of the path and search query before.
8+
9+
Custom converters should be updated to populate the full URL instead.

packages/open-next/src/adapters/edge-adapter.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import type { OpenNextHandlerOptions } from "types/overrides";
88
// We import it like that so that the edge plugin can replace it
99
import { NextConfig } from "../adapters/config";
1010
import { createGenericHandler } from "../core/createGenericHandler";
11-
import {
12-
convertBodyToReadableStream,
13-
convertToQueryString,
14-
} from "../core/routing/util";
11+
import { convertBodyToReadableStream } from "../core/routing/util";
1512

1613
globalThis.__openNextAls = new AsyncLocalStorage();
1714

@@ -25,13 +22,6 @@ const defaultHandler = async (
2522
return runWithOpenNextRequestContext(
2623
{ isISRRevalidation: false, waitUntil: options?.waitUntil },
2724
async () => {
28-
const host = internalEvent.headers.host
29-
? `https://${internalEvent.headers.host}`
30-
: "http://localhost:3000";
31-
const initialUrl = new URL(internalEvent.rawPath, host);
32-
initialUrl.search = convertToQueryString(internalEvent.query);
33-
const url = initialUrl.toString();
34-
3525
// @ts-expect-error - This is bundled
3626
const handler = await import("./middleware.mjs");
3727

@@ -43,7 +33,7 @@ const defaultHandler = async (
4333
i18n: NextConfig.i18n,
4434
trailingSlash: NextConfig.trailingSlash,
4535
},
46-
url,
36+
url: internalEvent.url,
4737
body: convertBodyToReadableStream(
4838
internalEvent.method,
4939
internalEvent.body,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
resolveQueue,
1616
resolveTagCache,
1717
} from "../core/resolve";
18+
import { constructNextUrl } from "../core/routing/util";
1819
import routingHandler, {
1920
INTERNAL_HEADER_INITIAL_PATH,
2021
INTERNAL_HEADER_RESOLVED_ROUTES,
@@ -90,7 +91,7 @@ const defaultHandler = async (
9091
internalEvent: {
9192
...result.internalEvent,
9293
rawPath: "/500",
93-
url: "/500",
94+
url: constructNextUrl(result.internalEvent.url, "/500"),
9495
method: "GET",
9596
},
9697
// On error we need to rewrite to the 500 page which is an internal rewrite

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import { runWithOpenNextRequestContext } from "utils/promise";
1313
import type { OpenNextHandlerOptions } from "types/overrides";
1414
import { debug, error, warn } from "../adapters/logger";
1515
import { patchAsyncStorage } from "./patchAsyncStorage";
16-
import { convertRes, createServerResponse } from "./routing/util";
16+
import {
17+
constructNextUrl,
18+
convertRes,
19+
createServerResponse,
20+
} from "./routing/util";
1721
import routingHandler, {
1822
INTERNAL_HEADER_INITIAL_PATH,
1923
INTERNAL_HEADER_RESOLVED_ROUTES,
@@ -102,7 +106,7 @@ export async function openNextHandler(
102106
rawPath: "/500",
103107
method: "GET",
104108
headers: {},
105-
url: "/500",
109+
url: constructNextUrl(internalEvent.url, "/500"),
106110
query: {},
107111
cookies: {},
108112
remoteAddress: "",
@@ -146,12 +150,13 @@ export async function openNextHandler(
146150

147151
const preprocessedEvent = routingResult.internalEvent;
148152
debug("preprocessedEvent", preprocessedEvent);
153+
const { search, pathname, hash } = new URL(preprocessedEvent.url);
149154
const reqProps = {
150155
method: preprocessedEvent.method,
151-
url: preprocessedEvent.url,
156+
url: `${pathname}${search}${hash}`,
152157
//WORKAROUND: We pass this header to the serverless function to mimic a prefetch request which will not trigger revalidation since we handle revalidation differently
153158
// There is 3 way we can handle revalidation:
154-
// 1. We could just let the revalidation go as normal, but due to race condtions the revalidation will be unreliable
159+
// 1. We could just let the revalidation go as normal, but due to race conditions the revalidation will be unreliable
155160
// 2. We could alter the lastModified time of our cache to make next believe that the cache is fresh, but this could cause issues with stale data since the cdn will cache the stale data as if it was fresh
156161
// 3. OUR CHOICE: We could pass a purpose prefetch header to the serverless function to make next believe that the request is a prefetch request and not trigger revalidation (This could potentially break in the future if next changes the behavior of prefetch requests)
157162
headers: { ...headers, purpose: "prefetch" },

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

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { emptyReadableStream, toReadableStream } from "utils/stream";
1414
import { debug } from "../../adapters/logger";
1515
import { localizePath } from "./i18n";
1616
import {
17+
constructNextUrl,
1718
convertFromQueryString,
1819
convertToQueryString,
1920
escapeRegex,
@@ -171,7 +172,7 @@ export function handleRewrites<T extends RewriteDefinition>(
171172
event: InternalEvent,
172173
rewrites: T[],
173174
) {
174-
const { rawPath, headers, query, cookies } = event;
175+
const { rawPath, headers, query, cookies, url } = event;
175176
const localizedRawPath = localizePath(event);
176177
const matcher = routeHasMatcher(headers, cookies, query);
177178
const computeHas = computeParamHas(headers, cookies, query);
@@ -185,7 +186,7 @@ export function handleRewrites<T extends RewriteDefinition>(
185186
});
186187
let finalQuery = query;
187188

188-
let rewrittenUrl = rawPath;
189+
let rewrittenUrl = url;
189190
const isExternalRewrite = isExternal(rewrite?.destination);
190191
debug("isExternalRewrite", isExternalRewrite);
191192
if (rewrite) {
@@ -202,16 +203,12 @@ export function handleRewrites<T extends RewriteDefinition>(
202203
// %2B does not get interpreted as a space in the URL thus breaking the query string.
203204
const encodePlusQueryString = queryString.replaceAll("+", "%20");
204205
debug("urlParts", { pathname, protocol, hostname, queryString });
205-
const toDestinationPath = compile(escapeRegex(pathname ?? "") ?? "");
206-
const toDestinationHost = compile(escapeRegex(hostname ?? "") ?? "");
207-
const toDestinationQuery = compile(
208-
escapeRegex(encodePlusQueryString ?? "") ?? "",
209-
);
206+
const toDestinationPath = compile(escapeRegex(pathname));
207+
const toDestinationHost = compile(escapeRegex(hostname));
208+
const toDestinationQuery = compile(escapeRegex(encodePlusQueryString));
210209
const params = {
211210
// params for the source
212-
...getParamsFromSource(match(escapeRegex(rewrite?.source) ?? ""))(
213-
pathToUse,
214-
),
211+
...getParamsFromSource(match(escapeRegex(rewrite.source)))(pathToUse),
215212
// params for the has
216213
...rewrite.has?.reduce((acc, cur) => {
217214
return Object.assign(acc, computeHas(cur));
@@ -232,20 +229,22 @@ export function handleRewrites<T extends RewriteDefinition>(
232229
}
233230
rewrittenUrl = isExternalRewrite
234231
? `${protocol}//${rewrittenHost}${rewrittenPath}`
235-
: `${rewrittenPath}`;
232+
: new URL(rewrittenPath, event.url).href;
233+
236234
// Should we merge the query params or use only the ones from the rewrite?
237235
finalQuery = {
238236
...query,
239237
...convertFromQueryString(rewrittenQuery),
240238
};
239+
rewrittenUrl += convertToQueryString(finalQuery);
241240
debug("rewrittenUrl", { rewrittenUrl, finalQuery, isUsingParams });
242241
}
243242

244243
return {
245244
internalEvent: {
246245
...event,
247-
rawPath: rewrittenUrl,
248-
url: `${rewrittenUrl}${convertToQueryString(finalQuery)}`,
246+
rawPath: new URL(rewrittenUrl).pathname,
247+
url: rewrittenUrl,
249248
},
250249
__rewrite: rewrite,
251250
isExternalRewrite,
@@ -365,7 +364,10 @@ export function fixDataPage(
365364
...internalEvent,
366365
rawPath: newPath,
367366
query,
368-
url: `${newPath}${convertToQueryString(query)}`,
367+
url: new URL(
368+
`${newPath}${convertToQueryString(query)}`,
369+
internalEvent.url,
370+
).href,
369371
};
370372
}
371373
return internalEvent;
@@ -397,7 +399,7 @@ export function handleFallbackFalse(
397399
event: {
398400
...internalEvent,
399401
rawPath: "/404",
400-
url: "/404",
402+
url: constructNextUrl(internalEvent.url, "/404"),
401403
headers: {
402404
...internalEvent.headers,
403405
"x-invoke-status": "404",

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

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,9 @@ export async function handleMiddleware(
5656
const hasMatch = middleMatch.some((r) => r.test(normalizedPath));
5757
if (!hasMatch) return internalEvent;
5858

59-
// Retrieve the protocol:
60-
// - In lambda, the url only contains the rawPath and the query - default to https
61-
// - In cloudflare, the protocol is usually http in dev and https in production
62-
const protocol = internalEvent.url.startsWith("http://") ? "http:" : "https:";
63-
64-
const host = headers.host
65-
? `${protocol}//${headers.host}`
66-
: "http://localhost:3000";
67-
68-
const initialUrl = new URL(normalizedPath, host);
59+
const initialUrl = new URL(normalizedPath, internalEvent.url);
6960
initialUrl.search = convertToQueryString(internalEvent.query);
70-
const url = initialUrl.toString();
61+
const url = initialUrl.href;
7162

7263
const middleware = await middlewareLoader();
7364

@@ -131,12 +122,7 @@ export async function handleMiddleware(
131122
// the redirected url and end the response.
132123
if (statusCode >= 300 && statusCode < 400) {
133124
resHeaders.location =
134-
responseHeaders
135-
.get("location")
136-
?.replace(
137-
"http://localhost:3000",
138-
`${protocol}//${internalEvent.headers.host}`,
139-
) ?? resHeaders.location;
125+
responseHeaders.get("location") ?? resHeaders.location;
140126
// res.setHeader("Location", location);
141127
return {
142128
body: emptyReadableStream(),
@@ -155,28 +141,24 @@ export async function handleMiddleware(
155141
let middlewareQueryString = internalEvent.query;
156142
let newUrl = internalEvent.url;
157143
if (rewriteUrl) {
144+
newUrl = rewriteUrl;
145+
rewritten = true;
158146
// If not a string, it should probably throw
159-
if (isExternal(rewriteUrl, internalEvent.headers.host as string)) {
160-
newUrl = rewriteUrl;
161-
rewritten = true;
147+
if (isExternal(newUrl, internalEvent.headers.host as string)) {
162148
isExternalRewrite = true;
163149
} else {
164150
const rewriteUrlObject = new URL(rewriteUrl);
165-
newUrl = rewriteUrlObject.pathname;
166151

167152
// Reset the query params if the middleware is a rewrite
168-
if (middlewareQueryString.__nextDataReq) {
169-
middlewareQueryString = {
170-
__nextDataReq: middlewareQueryString.__nextDataReq,
171-
};
172-
} else {
173-
middlewareQueryString = {};
174-
}
153+
middlewareQueryString = middlewareQueryString.__nextDataReq
154+
? {
155+
__nextDataReq: middlewareQueryString.__nextDataReq,
156+
}
157+
: {};
175158

176159
rewriteUrlObject.searchParams.forEach((v: string, k: string) => {
177160
middlewareQueryString[k] = v;
178161
});
179-
rewritten = true;
180162
}
181163
}
182164

@@ -198,9 +180,7 @@ export async function handleMiddleware(
198180
return {
199181
responseHeaders: resHeaders,
200182
url: newUrl,
201-
rawPath: rewritten
202-
? (newUrl ?? internalEvent.rawPath)
203-
: internalEvent.rawPath,
183+
rawPath: new URL(newUrl).pathname,
204184
type: internalEvent.type,
205185
headers: { ...internalEvent.headers, ...reqHeaders },
206186
body: internalEvent.body,

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,21 @@ export function getUrlParts(url: string, isExternal: boolean) {
7474
};
7575
}
7676

77+
/**
78+
* Creates an URL to a Next page
79+
*
80+
* @param baseUrl Used to get the origin
81+
* @param path The pathname
82+
* @returns The Next URL considering the basePath
83+
*
84+
* @__PURE__
85+
*/
86+
export function constructNextUrl(baseUrl: string, path: string) {
87+
const nextBasePath = NextConfig.basePath;
88+
const url = new URL(`${nextBasePath}${path}`, baseUrl);
89+
return url.href;
90+
}
91+
7792
/**
7893
*
7994
* @__PURE__

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
dynamicRouteMatcher,
2727
staticRouteMatcher,
2828
} from "./routing/routeMatcher";
29+
import { constructNextUrl } from "./routing/util";
2930

3031
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
3132
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
@@ -174,7 +175,7 @@ export default async function routingHandler(
174175
internalEvent = {
175176
...internalEvent,
176177
rawPath: "/404",
177-
url: "/404",
178+
url: constructNextUrl(internalEvent.url, "/404"),
178179
headers: {
179180
...internalEvent.headers,
180181
"x-middleware-response-cache-control":

packages/open-next/src/overrides/converters/aws-apigw-v1.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Converter } from "types/overrides";
44
import { fromReadableStream } from "utils/stream";
55

66
import { debug } from "../../adapters/logger";
7-
import { removeUndefinedFromQuery } from "./utils";
7+
import { extractHostFromHeaders, removeUndefinedFromQuery } from "./utils";
88

99
function normalizeAPIGatewayProxyEventHeaders(
1010
event: APIGatewayProxyEvent,
@@ -57,13 +57,14 @@ async function convertFromAPIGatewayProxyEvent(
5757
event: APIGatewayProxyEvent,
5858
): Promise<InternalEvent> {
5959
const { path, body, httpMethod, requestContext, isBase64Encoded } = event;
60+
const headers = normalizeAPIGatewayProxyEventHeaders(event);
6061
return {
6162
type: "core",
6263
method: httpMethod,
6364
rawPath: path,
64-
url: path + normalizeAPIGatewayProxyEventQueryParams(event),
65+
url: `https://${extractHostFromHeaders(headers)}${path}${normalizeAPIGatewayProxyEventQueryParams(event)}`,
6566
body: Buffer.from(body ?? "", isBase64Encoded ? "base64" : "utf8"),
66-
headers: normalizeAPIGatewayProxyEventHeaders(event),
67+
headers,
6768
remoteAddress: requestContext.identity.sourceIp,
6869
query: removeUndefinedFromQuery(
6970
event.multiValueQueryStringParameters ?? {},

packages/open-next/src/overrides/converters/aws-apigw-v2.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { fromReadableStream } from "utils/stream";
99

1010
import { debug } from "../../adapters/logger";
1111
import { convertToQuery } from "../../core/routing/util";
12-
import { removeUndefinedFromQuery } from "./utils";
12+
import { extractHostFromHeaders, removeUndefinedFromQuery } from "./utils";
1313

1414
// Not sure which one is really needed as this is not documented anywhere but server actions redirect are not working without this,
1515
// it causes a 500 error from cloudfront itself with a 'x-amzErrortype: InternalFailure' header
@@ -75,13 +75,14 @@ async function convertFromAPIGatewayProxyEventV2(
7575
event: APIGatewayProxyEventV2,
7676
): Promise<InternalEvent> {
7777
const { rawPath, rawQueryString, requestContext } = event;
78+
const headers = normalizeAPIGatewayProxyEventV2Headers(event);
7879
return {
7980
type: "core",
8081
method: requestContext.http.method,
8182
rawPath,
82-
url: rawPath + (rawQueryString ? `?${rawQueryString}` : ""),
83+
url: `https://${extractHostFromHeaders(headers)}${rawPath}${rawQueryString ? `?${rawQueryString}` : ""}`,
8384
body: normalizeAPIGatewayProxyEventV2Body(event),
84-
headers: normalizeAPIGatewayProxyEventV2Headers(event),
85+
headers,
8586
remoteAddress: requestContext.http.sourceIp,
8687
query: removeUndefinedFromQuery(convertToQuery(rawQueryString)),
8788
cookies:

0 commit comments

Comments
 (0)