Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 910b57f

Browse files
conico974vicb
andauthored
Fix middleware fetch (#823)
* fix middleware for directly returned fetch * return middleware response even for empty request * add e2e test * changeset * lint fix * fix unit test * Apply suggestions from code review Co-authored-by: Victor Berchet <[email protected]> --------- Co-authored-by: Victor Berchet <[email protected]>
1 parent 73281c9 commit 910b57f

File tree

5 files changed

+57
-35
lines changed

5 files changed

+57
-35
lines changed

.changeset/mean-icons-tap.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@opennextjs/aws": patch
3+
---
4+
5+
fix issue when returning fetch from the middleware
6+
Also fix an issue that prevented retunning response with an empty body in the middleware

examples/app-pages-router/middleware.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ export function middleware(request: NextRequest) {
4343
},
4444
});
4545
}
46+
47+
if (path === "/head" && request.method === "HEAD") {
48+
return new NextResponse(null, {
49+
headers: {
50+
"x-from-middleware": "true",
51+
},
52+
});
53+
}
54+
55+
if (path === "/fetch") {
56+
// This one test both that we don't modify immutable headers
57+
return fetch(new URL("/api/hello", request.url));
58+
}
4659
const rHeaders = new Headers(request.headers);
4760
const r = NextResponse.next({
4861
request: {

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

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -111,20 +111,23 @@ export async function handleMiddleware(
111111
const reqHeaders: Record<string, string> = {};
112112
const resHeaders: Record<string, string | string[]> = {};
113113

114-
responseHeaders.delete("x-middleware-override-headers");
115-
/* Next will set the header `x-middleware-set-cookie` when you `set-cookie` in the middleware.
116-
* We can delete it here since it will be set in `set-cookie` aswell. Next removes this header in the response themselves.
117-
* `x-middleware-next` is set when you invoke `NextResponse.next()`. We can delete it here aswell.
118-
*/
119-
responseHeaders.delete("x-middleware-set-cookie");
120-
responseHeaders.delete("x-middleware-next");
114+
// These are internal headers used by Next.js, we don't want to expose them to the client
115+
const filteredHeaders = [
116+
"x-middleware-override-headers",
117+
"x-middleware-set-cookie",
118+
"x-middleware-next",
119+
"x-middleware-rewrite",
120+
// We need to drop `content-encoding` because it will be decoded
121+
"content-encoding",
122+
];
121123

122124
const xMiddlewareKey = "x-middleware-request-";
123125
responseHeaders.forEach((value, key) => {
124126
if (key.startsWith(xMiddlewareKey)) {
125127
const k = key.substring(xMiddlewareKey.length);
126128
reqHeaders[k] = value;
127129
} else {
130+
if (filteredHeaders.includes(key.toLowerCase())) return;
128131
if (key.toLowerCase() === "set-cookie") {
129132
resHeaders[key] = resHeaders[key]
130133
? [...resHeaders[key], value]
@@ -135,21 +138,6 @@ export async function handleMiddleware(
135138
}
136139
});
137140

138-
// If the middleware returned a Redirect, we set the `Location` header with
139-
// the redirected url and end the response.
140-
if (statusCode >= 300 && statusCode < 400) {
141-
resHeaders.location =
142-
responseHeaders.get("location") ?? resHeaders.location;
143-
// res.setHeader("Location", location);
144-
return {
145-
body: emptyReadableStream(),
146-
type: internalEvent.type,
147-
statusCode: statusCode,
148-
headers: resHeaders,
149-
isBase64Encoded: false,
150-
} satisfies InternalResult;
151-
}
152-
153141
// If the middleware returned a Rewrite, set the `url` to the pathname of the rewrite
154142
// NOTE: the header was added to `req` from above
155143
const rewriteUrl = responseHeaders.get("x-middleware-rewrite");
@@ -177,11 +165,11 @@ export async function handleMiddleware(
177165
}
178166
}
179167

180-
// If the middleware returned a `NextResponse`, pipe the body to res. This will return
181-
// the body immediately to the client.
182-
if (result.body) {
168+
// If the middleware wants to directly return a response (i.e. not using `NextResponse.next()` or `NextResponse.rewrite()`)
169+
// we return the response directly
170+
if (!rewriteUrl && !responseHeaders.get("x-middleware-next")) {
183171
// transfer response body to res
184-
const body = result.body as ReadableStream<Uint8Array>;
172+
const body = (result.body as ReadableStream) ?? emptyReadableStream();
185173

186174
return {
187175
type: internalEvent.type,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test("should return correctly on HEAD request with an empty body", async ({
4+
request,
5+
}) => {
6+
const response = await request.head("/head");
7+
expect(response.status()).toBe(200);
8+
const body = await response.text();
9+
expect(body).toBe("");
10+
expect(response.headers()["x-from-middleware"]).toBe("true");
11+
});
12+
13+
test("should return correctly for directly returning a fetch response", async ({
14+
request,
15+
}) => {
16+
const response = await request.get("/fetch");
17+
expect(response.status()).toBe(200);
18+
const body = await response.json();
19+
expect(body).toEqual({ hello: "world" });
20+
});

packages/tests-unit/tests/core/routing/middleware.test.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,7 @@ describe("handleMiddleware", () => {
177177
...event,
178178
rawPath: "/rewrite",
179179
url: "http://localhost/rewrite",
180-
responseHeaders: {
181-
"x-middleware-rewrite": "http://localhost/rewrite",
182-
},
180+
responseHeaders: {},
183181
isExternalRewrite: false,
184182
});
185183
});
@@ -203,9 +201,7 @@ describe("handleMiddleware", () => {
203201
...event,
204202
rawPath: "/rewrite",
205203
url: "http://localhost/rewrite?newKey=value",
206-
responseHeaders: {
207-
"x-middleware-rewrite": "http://localhost/rewrite?newKey=value",
208-
},
204+
responseHeaders: {},
209205
query: {
210206
__nextDataReq: "1",
211207
newKey: "value",
@@ -232,9 +228,7 @@ describe("handleMiddleware", () => {
232228
...event,
233229
rawPath: "/rewrite",
234230
url: "http://external/rewrite",
235-
responseHeaders: {
236-
"x-middleware-rewrite": "http://external/rewrite",
237-
},
231+
responseHeaders: {},
238232
isExternalRewrite: true,
239233
});
240234
});
@@ -244,6 +238,7 @@ describe("handleMiddleware", () => {
244238
middleware.mockResolvedValue({
245239
headers: new Headers({
246240
"x-middleware-request-custom-header": "value",
241+
"x-middleware-next": "1",
247242
}),
248243
});
249244
const result = await handleMiddleware(event, "", middlewareLoader);

0 commit comments

Comments
 (0)