From 572d91f7e0c2c29e6840116c24b3fbf9df4fbdfd Mon Sep 17 00:00:00 2001 From: peterthenelson Date: Fri, 25 Oct 2024 17:22:46 -0400 Subject: [PATCH 1/5] Apply headers from routeRules in the dev server. - These otherwise fail to be applied to public assets (see #2749). - This change replies them redundantly in the other cases (where they were already being applied), but the alternative would require duplicating or reversing the URL / file path logic from the "send" package. fixes #2749 --- src/core/dev-server/server.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/core/dev-server/server.ts b/src/core/dev-server/server.ts index 7fd90cef37..4f4046548c 100644 --- a/src/core/dev-server/server.ts +++ b/src/core/dev-server/server.ts @@ -8,6 +8,7 @@ import { type FSWatcher, watch } from "chokidar"; import { type H3Error, type H3Event, + appendResponseHeaders, createApp, createError, eventHandler, @@ -22,15 +23,18 @@ import type { Nitro, NitroBuildInfo, NitroDevServer, + NitroRouteRules, NitroWorker, } from "nitropack/types"; import { resolve } from "pathe"; import { debounce } from "perfect-debounce"; import { servePlaceholder } from "serve-placeholder"; import serveStatic from "serve-static"; -import { joinURL } from "ufo"; +import { joinURL, withoutBase } from "ufo"; import defaultErrorHandler from "./error"; import { createVFSHandler } from "./vfs"; +import { createRouter as createRadixRouter, toRouteMatcher } from "radix3"; +import defu from "defu"; function initWorker(filename: string): Promise | undefined { if (!existsSync(filename)) { @@ -166,6 +170,20 @@ export function createDevServer(nitro: Nitro): NitroDevServer { // Debugging endpoint to view vfs app.use("/_vfs", createVFSHandler(nitro)); + // Apply headers based on routeRules. In cases where the dev server is being + // used, these would otherwise fail to be applied to public assets. + const routeRulesMatcher = toRouteMatcher( + createRadixRouter({ routes: nitro.options.routeRules }) + ); + app.use("/", eventHandler((event) => { + const baseUrl = nitro.options.runtimeConfig.app.baseURL; + const normPath = withoutBase(event.path.split("?")[0], baseUrl); + const rules: NitroRouteRules = defu({}, ...routeRulesMatcher.matchAll(normPath).reverse()); + if (rules.headers) { + appendResponseHeaders(event, rules.headers); + } + })); + // Serve asset dirs for (const asset of nitro.options.publicAssets) { const url = joinURL( From 704877a6c1103ee48e6002ea4f49fa075df27c68 Mon Sep 17 00:00:00 2001 From: peterthenelson Date: Fri, 25 Oct 2024 19:20:40 -0400 Subject: [PATCH 2/5] Fix lint errors --- src/core/dev-server/server.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/core/dev-server/server.ts b/src/core/dev-server/server.ts index 4f4046548c..9e6445bcc3 100644 --- a/src/core/dev-server/server.ts +++ b/src/core/dev-server/server.ts @@ -175,14 +175,20 @@ export function createDevServer(nitro: Nitro): NitroDevServer { const routeRulesMatcher = toRouteMatcher( createRadixRouter({ routes: nitro.options.routeRules }) ); - app.use("/", eventHandler((event) => { - const baseUrl = nitro.options.runtimeConfig.app.baseURL; - const normPath = withoutBase(event.path.split("?")[0], baseUrl); - const rules: NitroRouteRules = defu({}, ...routeRulesMatcher.matchAll(normPath).reverse()); - if (rules.headers) { - appendResponseHeaders(event, rules.headers); - } - })); + app.use( + "/", + eventHandler((event) => { + const baseUrl = nitro.options.runtimeConfig.app.baseURL; + const normPath = withoutBase(event.path.split("?")[0], baseUrl); + const rules: NitroRouteRules = defu( + {}, + ...routeRulesMatcher.matchAll(normPath).reverse() + ); + if (rules.headers) { + appendResponseHeaders(event, rules.headers); + } + }) + ); // Serve asset dirs for (const asset of nitro.options.publicAssets) { From e171882a10ad6b8f4f8d607dc8b77d531646e6d1 Mon Sep 17 00:00:00 2001 From: peterthenelson Date: Thu, 7 Nov 2024 09:52:40 -0500 Subject: [PATCH 3/5] Only apply dev headers to serve-static routes --- src/core/dev-server/server.ts | 42 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/core/dev-server/server.ts b/src/core/dev-server/server.ts index 9e6445bcc3..0007726020 100644 --- a/src/core/dev-server/server.ts +++ b/src/core/dev-server/server.ts @@ -8,7 +8,6 @@ import { type FSWatcher, watch } from "chokidar"; import { type H3Error, type H3Event, - appendResponseHeaders, createApp, createError, eventHandler, @@ -30,7 +29,7 @@ import { resolve } from "pathe"; import { debounce } from "perfect-debounce"; import { servePlaceholder } from "serve-placeholder"; import serveStatic from "serve-static"; -import { joinURL, withoutBase } from "ufo"; +import { joinURL } from "ufo"; import defaultErrorHandler from "./error"; import { createVFSHandler } from "./vfs"; import { createRouter as createRadixRouter, toRouteMatcher } from "radix3"; @@ -175,28 +174,35 @@ export function createDevServer(nitro: Nitro): NitroDevServer { const routeRulesMatcher = toRouteMatcher( createRadixRouter({ routes: nitro.options.routeRules }) ); - app.use( - "/", - eventHandler((event) => { - const baseUrl = nitro.options.runtimeConfig.app.baseURL; - const normPath = withoutBase(event.path.split("?")[0], baseUrl); - const rules: NitroRouteRules = defu( - {}, - ...routeRulesMatcher.matchAll(normPath).reverse() - ); - if (rules.headers) { - appendResponseHeaders(event, rules.headers); - } - }) - ); - // Serve asset dirs for (const asset of nitro.options.publicAssets) { const url = joinURL( nitro.options.runtimeConfig.app.baseURL, asset.baseURL || "/" ); - app.use(url, fromNodeMiddleware(serveStatic(asset.dir))); + app.use( + url, + fromNodeMiddleware( + serveStatic(asset.dir, { + setHeaders: (res) => { + const path = res.req.url; + if (path === undefined) { + return; + } + const rules: NitroRouteRules = defu( + {}, + ...routeRulesMatcher.matchAll(path).reverse() + ); + if (!rules.headers) { + return; + } + for (const [k, v] of Object.entries(rules.headers)) { + res.appendHeader(k, v); + } + }, + }) + ) + ); if (!asset.fallthrough) { app.use(url, fromNodeMiddleware(servePlaceholder())); } From 0f50bf109192fa031da55e05ee447b568e17197c Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 7 Nov 2024 20:45:58 +0100 Subject: [PATCH 4/5] add test --- test/fixture/nitro.config.ts | 1 + test/presets/nitro-dev.test.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/test/fixture/nitro.config.ts b/test/fixture/nitro.config.ts index 63d8eca4cd..1d0a985530 100644 --- a/test/fixture/nitro.config.ts +++ b/test/fixture/nitro.config.ts @@ -91,6 +91,7 @@ export default defineNitroConfig({ "/rules/_/cached/noncached": { cache: false, swr: false, isr: false }, "/rules/_/cached/**": { swr: true }, "/api/proxy/**": { proxy: "/api/echo" }, + "/build/**": { headers: { "x-build-header": "works" } }, }, prerender: { crawlLinks: true, diff --git a/test/presets/nitro-dev.test.ts b/test/presets/nitro-dev.test.ts index a7596b4bd9..40866f93f4 100644 --- a/test/presets/nitro-dev.test.ts +++ b/test/presets/nitro-dev.test.ts @@ -29,6 +29,22 @@ describe.skipIf(isCI)("nitro:preset:nitro-dev", async () => { expect(data.keys).includes("src:nitro.config.ts"); }); + it("static asset headers", async () => { + const { headers } = await ctx.fetch("/build/test.txt"); + expect(Object.fromEntries(headers)).toMatchObject({ + "accept-ranges": "bytes", + "cache-control": "public, max-age=0", + "last-modified": expect.any(String), + etag: 'W/"7-18df5a508c5"', + "content-type": "text/plain; charset=UTF-8", + "content-length": "7", + date: expect.any(String), + connection: "keep-alive", + "keep-alive": "timeout=5", + "x-build-header": "works", + }); + }); + describe("openAPI", () => { let spec: OpenAPI3; it("/_openapi.json", async () => { From d3257c856ccb873654fd5ee9af0807f60b54a0f4 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 7 Nov 2024 20:51:50 +0100 Subject: [PATCH 5/5] update other fixtures --- test/presets/netlify-legacy.test.ts | 27 ++++++++++++++------------- test/presets/netlify.test.ts | 27 ++++++++++++++------------- test/presets/vercel.test.ts | 1 + 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/test/presets/netlify-legacy.test.ts b/test/presets/netlify-legacy.test.ts index 5de5771d7f..33e8ba4733 100644 --- a/test/presets/netlify-legacy.test.ts +++ b/test/presets/netlify-legacy.test.ts @@ -82,19 +82,20 @@ describe("nitro:preset:netlify-legacy", async () => { ); expect(headers).toMatchInlineSnapshot(` - "/rules/headers - cache-control: s-maxage=60 - /rules/cors - access-control-allow-origin: * - access-control-allow-methods: GET - access-control-allow-headers: * - access-control-max-age: 0 - /rules/nested/* - x-test: test - /build/* - cache-control: public, max-age=3600, immutable - " - `); + "/rules/headers + cache-control: s-maxage=60 + /rules/cors + access-control-allow-origin: * + access-control-allow-methods: GET + access-control-allow-headers: * + access-control-max-age: 0 + /rules/nested/* + x-test: test + /build/* + cache-control: public, max-age=3600, immutable + x-build-header: works + " + `); }); it("should write config.json", async () => { const config = await fsp diff --git a/test/presets/netlify.test.ts b/test/presets/netlify.test.ts index a74afb093b..54dc76705a 100644 --- a/test/presets/netlify.test.ts +++ b/test/presets/netlify.test.ts @@ -62,19 +62,20 @@ describe("nitro:preset:netlify", async () => { ); expect(headers).toMatchInlineSnapshot(` - "/rules/headers - cache-control: s-maxage=60 - /rules/cors - access-control-allow-origin: * - access-control-allow-methods: GET - access-control-allow-headers: * - access-control-max-age: 0 - /rules/nested/* - x-test: test - /build/* - cache-control: public, max-age=3600, immutable - " - `); + "/rules/headers + cache-control: s-maxage=60 + /rules/cors + access-control-allow-origin: * + access-control-allow-methods: GET + access-control-allow-headers: * + access-control-max-age: 0 + /rules/nested/* + x-test: test + /build/* + cache-control: public, max-age=3600, immutable + x-build-header: works + " + `); }); it("writes config.json", async () => { diff --git a/test/presets/vercel.test.ts b/test/presets/vercel.test.ts index 25ad76cfe2..902d806106 100644 --- a/test/presets/vercel.test.ts +++ b/test/presets/vercel.test.ts @@ -93,6 +93,7 @@ describe("nitro:preset:vercel", async () => { { "headers": { "cache-control": "public, max-age=3600, immutable", + "x-build-header": "works", }, "src": "/build/(.*)", },