Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/mean-icons-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@opennextjs/aws": patch
---

fix issue when returning fetch from the middleware
Also fix an issue that prevented retunning response with an empty body in the middleware
13 changes: 13 additions & 0 deletions examples/app-pages-router/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ export function middleware(request: NextRequest) {
},
});
}

if (path === "/head" && request.method === "HEAD") {
return new NextResponse(null, {
headers: {
"x-from-middleware": "true",
},
});
}

if (path === "/fetch") {
// This one test both that we don't modify immutable headers
return fetch(new URL("/api/hello", request.url));
}
const rHeaders = new Headers(request.headers);
const r = NextResponse.next({
request: {
Expand Down
40 changes: 14 additions & 26 deletions packages/open-next/src/core/routing/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,23 @@ export async function handleMiddleware(
const reqHeaders: Record<string, string> = {};
const resHeaders: Record<string, string | string[]> = {};

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

const xMiddlewareKey = "x-middleware-request-";
responseHeaders.forEach((value, key) => {
if (key.startsWith(xMiddlewareKey)) {
const k = key.substring(xMiddlewareKey.length);
reqHeaders[k] = value;
} else {
if (filteredHeaders.includes(key.toLowerCase())) return;
if (key.toLowerCase() === "set-cookie") {
resHeaders[key] = resHeaders[key]
? [...resHeaders[key], value]
Expand All @@ -135,21 +138,6 @@ export async function handleMiddleware(
}
});

// If the middleware returned a Redirect, we set the `Location` header with
// the redirected url and end the response.
if (statusCode >= 300 && statusCode < 400) {
resHeaders.location =
responseHeaders.get("location") ?? resHeaders.location;
// res.setHeader("Location", location);
return {
body: emptyReadableStream(),
type: internalEvent.type,
statusCode: statusCode,
headers: resHeaders,
isBase64Encoded: false,
} satisfies InternalResult;
}

// If the middleware returned a Rewrite, set the `url` to the pathname of the rewrite
// NOTE: the header was added to `req` from above
const rewriteUrl = responseHeaders.get("x-middleware-rewrite");
Expand Down Expand Up @@ -177,11 +165,11 @@ export async function handleMiddleware(
}
}

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

return {
type: internalEvent.type,
Expand Down
20 changes: 20 additions & 0 deletions packages/tests-e2e/tests/appPagesRouter/middleware.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, test } from "@playwright/test";

test("should return correctly on HEAD request with an empty body", async ({
request,
}) => {
const response = await request.head("/head");
const body = await response.text();
expect(response.status()).toBe(200);
expect(body).toBe("");
expect(response.headers()["x-from-middleware"]).toBe("true");
});

test("should return correctly for directly returning a fetch response", async ({
request,
}) => {
const response = await request.get("/fetch");
const body = await response.json();
expect(response.status()).toBe(200);
expect(body).toEqual({ hello: "world" });
});
13 changes: 4 additions & 9 deletions packages/tests-unit/tests/core/routing/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,7 @@ describe("handleMiddleware", () => {
...event,
rawPath: "/rewrite",
url: "http://localhost/rewrite",
responseHeaders: {
"x-middleware-rewrite": "http://localhost/rewrite",
},
responseHeaders: {},
isExternalRewrite: false,
});
});
Expand All @@ -203,9 +201,7 @@ describe("handleMiddleware", () => {
...event,
rawPath: "/rewrite",
url: "http://localhost/rewrite?newKey=value",
responseHeaders: {
"x-middleware-rewrite": "http://localhost/rewrite?newKey=value",
},
responseHeaders: {},
query: {
__nextDataReq: "1",
newKey: "value",
Expand All @@ -232,9 +228,7 @@ describe("handleMiddleware", () => {
...event,
rawPath: "/rewrite",
url: "http://external/rewrite",
responseHeaders: {
"x-middleware-rewrite": "http://external/rewrite",
},
responseHeaders: {},
isExternalRewrite: true,
});
});
Expand All @@ -244,6 +238,7 @@ describe("handleMiddleware", () => {
middleware.mockResolvedValue({
headers: new Headers({
"x-middleware-request-custom-header": "value",
"x-middleware-next": "1",
}),
});
const result = await handleMiddleware(event, "", middlewareLoader);
Expand Down
Loading