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
5 changes: 5 additions & 0 deletions .changeset/heavy-shrimps-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/start": patch
---

Return `404` when server-function is not found
72 changes: 45 additions & 27 deletions packages/start/src/runtime/server-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ import {
import { sharedConfig } from "solid-js";
import { renderToString } from "solid-js/web";
import { provideRequestEvent } from "solid-js/web/storage";
import { eventHandler, parseCookies, setHeader, setResponseStatus, type HTTPEvent } from "vinxi/http";
import {
eventHandler,
parseCookies,
setHeader,
setResponseStatus,
type HTTPEvent
} from "vinxi/http";
import invariant from "vinxi/lib/invariant";
import { cloneEvent, getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
import { getExpectedRedirectStatus } from "../server/handler";
Expand Down Expand Up @@ -87,14 +93,23 @@ async function handleServerFunction(h3Event: HTTPEvent) {
} else {
functionId = url.searchParams.get("id");
name = url.searchParams.get("name");
if (!functionId || !name) return new Response("Server function not found", { status: 404 });

if (!functionId || !name) {
return process.env.NODE_ENV === "development"
? new Response("Server function not found", { status: 404 })
: new Response(null, { status: 404 });
}
}

const serverFnInfo = serverFnManifest[functionId];
let fnModule: undefined | { [key: string]: any };

if (!serverFnInfo) return new Response("Server function not found", { status: 404 });

if (!serverFnInfo) {
return process.env.NODE_ENV === "development"
? new Response("Server function not found", { status: 404 })
: new Response(null, { status: 404 });
}

if (process.env.NODE_ENV === "development") {
// In dev, we use Vinxi to get the "server" server-side router
// Then we use that router's devServer.ssrLoadModule to get the serverFn
Expand All @@ -118,19 +133,19 @@ async function handleServerFunction(h3Event: HTTPEvent) {
const json = JSON.parse(args);
(json.t
? (fromJSON(json, {
plugins: [
CustomEventPlugin,
DOMExceptionPlugin,
EventPlugin,
FormDataPlugin,
HeadersPlugin,
ReadableStreamPlugin,
RequestPlugin,
ResponsePlugin,
URLSearchParamsPlugin,
URLPlugin
]
}) as any)
plugins: [
CustomEventPlugin,
DOMExceptionPlugin,
EventPlugin,
FormDataPlugin,
HeadersPlugin,
ReadableStreamPlugin,
RequestPlugin,
ResponsePlugin,
URLSearchParamsPlugin,
URLPlugin
]
}) as any)
: json
).forEach((arg: any) => parsed.push(arg));
}
Expand All @@ -148,7 +163,8 @@ async function handleServerFunction(h3Event: HTTPEvent) {
const isReadableStream = h3Request instanceof ReadableStream;
const hasReadableStream = (h3Request as EdgeIncomingMessage).body instanceof ReadableStream;
const isH3EventBodyStreamLocked =
(isReadableStream && h3Request.locked) || (hasReadableStream && ((h3Request as EdgeIncomingMessage).body as ReadableStream).locked);
(isReadableStream && h3Request.locked) ||
(hasReadableStream && ((h3Request as EdgeIncomingMessage).body as ReadableStream).locked);
const requestBody = isReadableStream ? h3Request : h3Request.body;

if (
Expand Down Expand Up @@ -272,13 +288,15 @@ function handleNoJS(result: any, request: Request, parsed: any[], thrown?: boole
if (result) {
headers.append(
"Set-Cookie",
`flash=${encodeURIComponent(JSON.stringify({
url: url.pathname + url.search,
result: isError ? result.message : result,
thrown: thrown,
error: isError,
input: [...parsed.slice(0, -1), [...parsed[parsed.length - 1].entries()]]
}))}; Secure; HttpOnly;`
`flash=${encodeURIComponent(
JSON.stringify({
url: url.pathname + url.search,
result: isError ? result.message : result,
thrown: thrown,
error: isError,
input: [...parsed.slice(0, -1), [...parsed[parsed.length - 1].entries()]]
})
)}; Secure; HttpOnly;`
);
}
return new Response(null, {
Expand All @@ -302,7 +320,7 @@ function createSingleFlightHeaders(sourceEvent: FetchEvent) {
useH3Internals = true;
sourceEvent.nativeEvent.node.req.headers.cookie = "";
}
SetCookies.forEach((cookie) => {
SetCookies.forEach(cookie => {
if (!cookie) return;
const keyValue = cookie.split(";")[0]!;
const [key, value] = keyValue.split("=");
Expand All @@ -311,7 +329,7 @@ function createSingleFlightHeaders(sourceEvent: FetchEvent) {
Object.entries(cookies).forEach(([key, value]) => {
headers.append("cookie", `${key}=${value}`);
useH3Internals && (sourceEvent.nativeEvent.node.req.headers.cookie += `${key}=${value};`);
})
});

return headers;
}
Expand Down
26 changes: 17 additions & 9 deletions packages/tests/cypress/e2e/server-function.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,46 @@ describe("server-function", () => {
it("should have isServer false in the client", () => {
cy.visit("/");
cy.get("#server-fn-test").contains('{"clientWithIsServer":false}');
})
});
it("should have isServer true in the server function - nested", () => {
cy.visit("/is-server-nested");
cy.get("#server-fn-test").contains('{"serverFnWithIsServer":true}');
})
});
it("should have an id of type string in the server function meta - nested", () => {
cy.visit("/server-function-meta-nested");
cy.get("#server-fn-test").contains('{"serverFnWithMeta":"string"}');
})
});
it("should externalize node builtin in server function - nested", () => {
cy.visit("/node-builtin-nested");
cy.get("#server-fn-test").contains('{"serverFnWithNodeBuiltin":"can/externalize"}');
})
});
it("should externalize npm module in server function - nested", () => {
cy.visit("npm-module-nested");
cy.get("#server-fn-test").contains('{"serverFnWithNpmModule":[2,4,6]}');
})
});

it("should have isServer true in the server function - toplevel", () => {
cy.visit("/is-server-toplevel");
cy.get("#server-fn-test").contains('{"serverFnWithIsServer":true}');
})
});
it("should have an id of type string in the server function meta - toplevel", () => {
cy.visit("/server-function-meta");
cy.get("#server-fn-test").contains('{"serverFnWithMeta":"string"}');
})
});
it("should externalize node builtin in server function - toplevel", () => {
cy.visit("/node-builtin-toplevel");
cy.get("#server-fn-test").contains('{"serverFnWithNodeBuiltin":"can/externalize"}');
})
});
it("should externalize npm module in server function - toplevel", () => {
cy.visit("npm-module-toplevel");
cy.get("#server-fn-test").contains('{"serverFnWithNpmModule":[2,4,6]}');
})
});
it("should throw a 404 if function is not found", () => {
cy.request({
url: "/_server/?name='not-found-function'",
failOnStatusCode: false
})
.its("status")
.should("eq", 404);
});
});