diff --git a/lib/index.js b/lib/index.js index 0ab7b71..60e86b6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -40,16 +40,23 @@ function vitePlugin(options) { async configureServer(server) { const nextSymbol = Symbol('next'); + /** @type {import('fastify').FastifyInstance} */ + let fastify; + /** @type {import('fastify').FastifyServerFactory} */ const serverFactory = (handler, opts) => { server.middlewares.use((req, res, next) => { - req[nextSymbol] = next; - handler(req, res); + if (fastify.hasRoute({ method: req.method, url: req.url })) { + req[nextSymbol] = next; + handler(req, res); + } else { + next(); + } }); return /** @type {import('http').Server} */(server.httpServer ?? new http.Server()); } - const fastify = Fastify({ + fastify = Fastify({ logger: options?.logger ?? true, serverFactory }); @@ -63,16 +70,17 @@ function vitePlugin(options) { } setupRoutes(fastify); } - - // Final catch-all route forwards back to the Vite server - fastify.all('/*', function (request) { + + // The vite socket can get to here... + // Relevant also when registered @fastify/websocket + fastify.get("/", function (request) { /** @type {import('connect').NextFunction} */ const next = request.raw[nextSymbol]; if (typeof next === "function") { next(); } }); - + await fastify.ready(); }, transform(code, id) { diff --git a/lib/server.js b/lib/server.js index 95b0871..3812a06 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,3 +1,4 @@ +import { IncomingMessage } from "node:http"; import { NodeApp } from "astro/app/node"; import Fastify from "fastify"; import fastifyStatic from "@fastify/static"; @@ -58,9 +59,13 @@ export function start(manifest, options) { * @param {import('fastify').FastifyReply} reply */ const rootHandler = async (request, reply) => { - const routeData = app.match(request.raw, { matchNotFound: true }); + let rawRequest = request.raw; + if (IncomingMessage.isDisturbed(rawRequest)) { + rawRequest = revertToRequest(request); + } + const routeData = app.match(rawRequest, { matchNotFound: true }); if (routeData) { - const response = await app.render(request.raw, { routeData }); + const response = await app.render(rawRequest, { routeData }); await writeWebResponse(app, reply.raw, response); } else { @@ -151,6 +156,60 @@ async function writeWebResponse(app, res, webResponse) { res.end(); } +/** + * @param {import('fastify').FastifyRequest} request + */ +function revertToRequest(request) { + const url = createURL(request); + const newRequest = new Request(url, { + method: request.method, + headers: request.headers, + body: getBody(request), + }); + return newRequest; +} + +/** + * @param {import('fastify').FastifyRequest} request + */ +function getBody(request) { + const contentType = request.headers["content-type"]; + switch (contentType) { + case "application/x-www-form-urlencoded": { + return Object.keys(request.body).reduce((urlEncode, key) => { + urlEncode.append(key, request.body[key]); + return urlEncode; + }, new URLSearchParams()); + } + default: + return request.body; + } +} + +/** + * @param {import('fastify').FastifyRequest} request + */ +function createURL(request) { + const url = new URL( + `${request.url}`, + `${request.protocol}://${request.hostname}`, + ); + if (typeof query === "string") { + url.search = query; + } else if (typeof query === "object") { + Object.entries(query).forEach(([key, val]) => { + if (typeof val === "string") { + url.searchParams.append(key, val); + } else { + val.forEach((innerVal) => { + url.searchParams.append(key, innerVal); + }); + } + }); + } + return url; +} + export function createExports(manifest, options) { return { start() { diff --git a/package-lock.json b/package-lock.json index 740029e..1c8e7e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "@astrojs/webapi": "^1.0.0", "@fastify/static": "^6.5.0", - "fastify": "^4.5.3" + "fastify": "^4.5.3", + "undici": "^6.6.2" }, "devDependencies": { "astro": "^4.3.5" @@ -883,6 +884,14 @@ "fast-uri": "^2.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "engines": { + "node": ">=14" + } + }, "node_modules/@fastify/error": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", @@ -6801,6 +6810,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/undici": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.6.2.tgz", + "integrity": "sha512-vSqvUE5skSxQJ5sztTZ/CdeJb1Wq0Hf44hlYMciqHghvz+K88U0l7D6u1VsndoFgskDcnU+nG3gYmMzJVzd9Qg==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=18.0" + } + }, "node_modules/unherit": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/unherit/-/unherit-3.0.1.tgz", @@ -7833,6 +7853,11 @@ "fast-uri": "^2.0.0" } }, + "@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" + }, "@fastify/error": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", @@ -12041,6 +12066,14 @@ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true }, + "undici": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.6.2.tgz", + "integrity": "sha512-vSqvUE5skSxQJ5sztTZ/CdeJb1Wq0Hf44hlYMciqHghvz+K88U0l7D6u1VsndoFgskDcnU+nG3gYmMzJVzd9Qg==", + "requires": { + "@fastify/busboy": "^2.0.0" + } + }, "unherit": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/unherit/-/unherit-3.0.1.tgz",