Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .changeset/fix-2131-middleware-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@solidjs/start": patch
---

fix: run `onBeforeResponse` middleware in declared order (#2131)

Applications that reversed their `onBeforeResponse` arrays as a workaround should restore the intended declaration order.
120 changes: 120 additions & 0 deletions packages/start/src/middleware/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { setTimeout } from "node:timers/promises";
import { defineHandler, H3, type Middleware } from "h3";
import { describe, expect, it, vi } from "vitest";

import { createMiddleware } from "./index.ts";

vi.mock("server-only", () => ({}));

const createApp = (middleware: Middleware[]) => {
const app = new H3();
app.use(defineHandler({ middleware, handler: () => "ok" }));
return app;
};

describe("createMiddleware", () => {
it("runs onRequest middleware in declared order", async () => {
const calls: string[] = [];
const app = createApp(
createMiddleware({
onRequest: [
async () => {
calls.push("one");
await setTimeout(5);
},
() => {
calls.push("two");
},
],
}),
);

await app.fetch(new Request("http://localhost/"));

expect(calls).toEqual(["one", "two"]);
});

// https://github.com/solidjs/solid-start/issues/2131
it("runs onBeforeResponse middleware in declared order", async () => {
const calls: string[] = [];
const app = createApp(
createMiddleware({
onBeforeResponse: [
async () => {
calls.push("one");
await setTimeout(5);
},
async () => {
calls.push("two");
await setTimeout(5);
},
() => {
calls.push("three");
},
],
}),
);

await app.fetch(new Request("http://localhost/"));

expect(calls).toEqual(["one", "two", "three"]);
});

it("passes replacement responses to later onBeforeResponse middleware", async () => {
const app = createApp(
createMiddleware({
onBeforeResponse: [
() => new Response("one"),
async (_event, response) => {
expect(response.body).toBeInstanceOf(Response);
return new Response(`${await (response.body as Response).text()} two`);
},
],
}),
);

const res = await app.fetch(new Request("http://localhost/"));

expect(await res.text()).toBe("one two");
});

it("runs onRequest before the handler and onBeforeResponse after", async () => {
const calls: string[] = [];
const app = new H3();
app.use(
defineHandler({
middleware: createMiddleware({
onRequest: [() => void calls.push("request")],
onBeforeResponse: [() => void calls.push("response")],
}),
handler: () => {
calls.push("handler");
return "ok";
},
}),
);

await app.fetch(new Request("http://localhost/"));

expect(calls).toEqual(["request", "handler", "response"]);
});

it("lets an onBeforeResponse middleware replace the response", async () => {
const app = createApp(
createMiddleware({
onBeforeResponse: () => new Response("replaced", { status: 418 }),
}),
);

const res = await app.fetch(new Request("http://localhost/"));

expect(res.status).toBe(418);
expect(await res.text()).toBe("replaced");
});

it("passes through an array of H3 middleware", () => {
const middleware: Middleware[] = [async (_event, next) => next()];

expect(createMiddleware(middleware)).toBe(middleware);
});
});
5 changes: 4 additions & 1 deletion packages/start/src/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ export function createMiddleware(
if (typeof args.onBeforeResponse === "function") {
mw.push(wrapResponseMiddleware(args.onBeforeResponse));
} else if (Array.isArray(args.onBeforeResponse)) {
mw.push(...args.onBeforeResponse.map(wrapResponseMiddleware));
// h3 middleware compose onion-style: logic after `await next()` unwinds
// innermost-first (reverse registration order). Register the wrappers
// reversed so onBeforeResponse functions run in their declared order.
mw.push(...args.onBeforeResponse.map(wrapResponseMiddleware).reverse());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katywings you probably know the most about middleware. Does this change make sense?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense yuppa 👍.

What I wonder is why we arent using h3's onRequest and onResponse helpers 🤔. (onRequest does pretty much nothing, but onResponse would simplify our wrapResponseMiddleware)

}

return mw;
Expand Down
Loading