Skip to content

Commit 7d60df5

Browse files
conico974Nicolas Dorseuil
andauthored
Add request ID handling (#914)
* add an env variable to append the request id to the headers * share requestId when using an external middleware * add e2e test * changeset --------- Co-authored-by: Nicolas Dorseuil <[email protected]>
1 parent 416af75 commit 7d60df5

File tree

10 files changed

+43
-12
lines changed

10 files changed

+43
-12
lines changed

.changeset/orange-badgers-cross.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+
add the OPEN_NEXT_REQUEST_ID_HEADER env variable that allow to always have the request id header

examples/sst/stacks/AppRouter.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,10 @@ export function AppRouter({ stack }) {
66
path: "../app-router",
77
environment: {
88
OPEN_NEXT_FORCE_NON_EMPTY_RESPONSE: "true",
9+
// We want to always add the request ID header
10+
OPEN_NEXT_REQUEST_ID_HEADER: "true",
911
},
1012
});
11-
// const site = new NextjsSite(stack, "approuter", {
12-
// path: "../app-router",
13-
// buildCommand: "npm run openbuild",
14-
// bind: [],
15-
// environment: {},
16-
// timeout: "20 seconds",
17-
// experimental: {
18-
// streaming: true,
19-
// },
20-
// });
2113

2214
stack.addOutputs({
2315
url: `https://${site.distribution.domainName}`,

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { OpenNextHandlerOptions } from "types/overrides";
99
import { NextConfig } from "../adapters/config";
1010
import { createGenericHandler } from "../core/createGenericHandler";
1111
import { convertBodyToReadableStream } from "../core/routing/util";
12+
import { INTERNAL_EVENT_REQUEST_ID } from "../core/routingHandler";
1213

1314
globalThis.__openNextAls = new AsyncLocalStorage();
1415

@@ -18,9 +19,13 @@ const defaultHandler = async (
1819
): Promise<InternalResult> => {
1920
globalThis.isEdgeRuntime = true;
2021

22+
const requestId = globalThis.openNextConfig.middleware?.external
23+
? internalEvent.headers[INTERNAL_EVENT_REQUEST_ID]
24+
: Math.random().toString(36);
25+
2126
// We run everything in the async local storage context so that it is available in edge runtime functions
2227
return runWithOpenNextRequestContext(
23-
{ isISRRevalidation: false, waitUntil: options?.waitUntil },
28+
{ isISRRevalidation: false, waitUntil: options?.waitUntil, requestId },
2429
async () => {
2530
// @ts-expect-error - This is bundled
2631
const handler = await import("./middleware.mjs");

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from "../core/resolve";
1818
import { constructNextUrl } from "../core/routing/util";
1919
import routingHandler, {
20+
INTERNAL_EVENT_REQUEST_ID,
2021
INTERNAL_HEADER_INITIAL_URL,
2122
INTERNAL_HEADER_RESOLVED_ROUTES,
2223
} from "../core/routingHandler";
@@ -50,11 +51,14 @@ const defaultHandler = async (
5051
);
5152
//#endOverride
5253

54+
const requestId = Math.random().toString(36);
55+
5356
// We run everything in the async local storage context so that it is available in the external middleware
5457
return runWithOpenNextRequestContext(
5558
{
5659
isISRRevalidation: internalEvent.headers["x-isr"] === "1",
5760
waitUntil: options?.waitUntil,
61+
requestId,
5862
},
5963
async () => {
6064
const result = await routingHandler(internalEvent);
@@ -74,6 +78,7 @@ const defaultHandler = async (
7478
[INTERNAL_HEADER_RESOLVED_ROUTES]: JSON.stringify(
7579
result.resolvedRoutes,
7680
),
81+
[INTERNAL_EVENT_REQUEST_ID]: requestId,
7782
},
7883
},
7984
isExternalRewrite: result.isExternalRewrite,
@@ -91,6 +96,10 @@ const defaultHandler = async (
9196
type: "middleware",
9297
internalEvent: {
9398
...result.internalEvent,
99+
headers: {
100+
...result.internalEvent.headers,
101+
[INTERNAL_EVENT_REQUEST_ID]: requestId,
102+
},
94103
rawPath: "/500",
95104
url: constructNextUrl(result.internalEvent.url, "/500"),
96105
method: "GET",
@@ -105,6 +114,7 @@ const defaultHandler = async (
105114
}
106115
}
107116

117+
result.headers[INTERNAL_EVENT_REQUEST_ID] = requestId;
108118
debug("Middleware response", result);
109119
return result;
110120
},

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
createServerResponse,
2222
} from "./routing/util";
2323
import routingHandler, {
24+
INTERNAL_EVENT_REQUEST_ID,
2425
INTERNAL_HEADER_INITIAL_URL,
2526
INTERNAL_HEADER_RESOLVED_ROUTES,
2627
MIDDLEWARE_HEADER_PREFIX,
@@ -40,11 +41,18 @@ export async function openNextHandler(
4041
options?: OpenNextHandlerOptions,
4142
): Promise<InternalResult> {
4243
const initialHeaders = internalEvent.headers;
44+
// We only use the requestId header if we are using an external middleware
45+
// This is to ensure that no one can spoof the requestId
46+
// When using an external middleware, we always assume that headers cannot be spoofed
47+
const requestId = globalThis.openNextConfig.middleware?.external
48+
? internalEvent.headers[INTERNAL_EVENT_REQUEST_ID]
49+
: Math.random().toString(36);
4350
// We run everything in the async local storage context so that it is available in the middleware as well as in NextServer
4451
return runWithOpenNextRequestContext(
4552
{
4653
isISRRevalidation: initialHeaders["x-isr"] === "1",
4754
waitUntil: options?.waitUntil,
55+
requestId,
4856
},
4957
async () => {
5058
await globalThis.__next_route_preloader("waitUntil");

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ export function addOpenNextHeader(headers: OutgoingHttpHeaders) {
281281
}
282282
if (globalThis.openNextDebug) {
283283
headers["X-OpenNext-Version"] = globalThis.openNextVersion;
284+
}
285+
if (process.env.OPEN_NEXT_REQUEST_ID_HEADER || globalThis.openNextDebug) {
284286
headers["X-OpenNext-RequestId"] =
285287
globalThis.__openNextAls.getStore()?.requestId;
286288
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const INTERNAL_HEADER_PREFIX = "x-opennext-";
3535
export const INTERNAL_HEADER_INITIAL_URL = `${INTERNAL_HEADER_PREFIX}initial-url`;
3636
export const INTERNAL_HEADER_LOCALE = `${INTERNAL_HEADER_PREFIX}locale`;
3737
export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
38+
export const INTERNAL_EVENT_REQUEST_ID = `${INTERNAL_HEADER_PREFIX}request-id`;
3839

3940
// Geolocation headers starting from Nextjs 15
4041
// See https://github.com/vercel/vercel/blob/7714b1c/packages/functions/src/headers.ts

packages/open-next/src/utils/promise.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,19 @@ export function runWithOpenNextRequestContext<T>(
105105
{
106106
isISRRevalidation,
107107
waitUntil,
108+
requestId = Math.random().toString(36),
108109
}: {
109110
// Whether we are in ISR revalidation
110111
isISRRevalidation: boolean;
111112
// Extends the liftetime of the runtime after the response is returned.
112113
waitUntil?: WaitUntil;
114+
requestId?: string;
113115
},
114116
fn: () => Promise<T>,
115117
): Promise<T> {
116118
return globalThis.__openNextAls.run(
117119
{
118-
requestId: Math.random().toString(36),
120+
requestId,
119121
pendingPromiseRunner: new DetachedPromiseRunner(),
120122
isISRRevalidation,
121123
waitUntil,

packages/tests-e2e/tests/appRouter/headers.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ test("Headers", async ({ page }) => {
2424
// Both these headers should not be present cause poweredByHeader is false in appRouter
2525
expect(headers["x-powered-by"]).toBeFalsy();
2626
expect(headers["x-opennext"]).toBeFalsy();
27+
28+
// Request ID header should be set
29+
expect(headers["x-opennext-requestid"]).not.toBeFalsy();
2730
});

packages/tests-e2e/tests/pagesRouter/header.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ test("should test if poweredByHeader adds the correct headers ", async ({
1111
// Both these headers should be present cause poweredByHeader is true in pagesRouter
1212
expect(headers?.["x-powered-by"]).toBe("Next.js");
1313
expect(headers?.["x-opennext"]).toBe("1");
14+
15+
// Request ID header should not be set
16+
expect(headers?.["x-opennext-requestid"]).toBeUndefined();
1417
});

0 commit comments

Comments
 (0)