diff --git a/.changeset/sharp-nails-complain.md b/.changeset/sharp-nails-complain.md new file mode 100644 index 0000000000..9e0ad62085 --- /dev/null +++ b/.changeset/sharp-nails-complain.md @@ -0,0 +1,5 @@ +--- +"@react-router/express": minor +--- + +Add support for web servers other than express. diff --git a/contributors.yml b/contributors.yml index 6cd23b1758..a603914de0 100644 --- a/contributors.yml +++ b/contributors.yml @@ -1,5 +1,6 @@ - 0xEddie - 3fuyang +- 43081j - aarbi - abdallah-nour - abeadam diff --git a/packages/react-router-express/server.ts b/packages/react-router-express/server.ts index fa67aaf4d7..9a5a16e877 100644 --- a/packages/react-router-express/server.ts +++ b/packages/react-router-express/server.ts @@ -14,6 +14,8 @@ import { createReadableStreamFromReadable, writeReadableStreamToWritable, } from "@react-router/node"; +import type { IncomingMessage, ServerResponse } from "node:http"; +import { TLSSocket } from "node:tls"; type MaybePromise = T | Promise; @@ -26,15 +28,15 @@ type MaybePromise = T | Promise; * values that are generated by Express middleware like `req.session`. */ export type GetLoadContextFunction = ( - req: express.Request, - res: express.Response + req: IncomingMessage, + res: ServerResponse ) => MiddlewareEnabled extends true ? MaybePromise : MaybePromise; export type RequestHandler = ( - req: express.Request, - res: express.Response, + req: IncomingMessage, + res: ServerResponse, next: express.NextFunction ) => Promise; @@ -53,9 +55,9 @@ export function createRequestHandler({ let handleRequest = createRemixRequestHandler(build, mode); return async ( - req: express.Request, - res: express.Response, - next: express.NextFunction + req: IncomingMessage, + res: ServerResponse, + next: (err?: unknown) => void ) => { try { let request = createRemixRequest(req, res); @@ -73,7 +75,7 @@ export function createRequestHandler({ } export function createRemixHeaders( - requestHeaders: express.Request["headers"] + requestHeaders: IncomingMessage["headers"] ): Headers { let headers = new Headers(); @@ -93,13 +95,16 @@ export function createRemixHeaders( } export function createRemixRequest( - req: express.Request, - res: express.Response + req: IncomingMessage, + res: ServerResponse ): Request { // req.hostname doesn't include port information so grab that from // `X-Forwarded-Host` or `Host` - let [, hostnamePortStr] = req.get("X-Forwarded-Host")?.split(":") ?? []; - let [, hostPortStr] = req.get("host")?.split(":") ?? []; + let [, hostnamePortStr] = + typeof req.headers["X-Forwarded-Host"] === "string" + ? req.headers["X-Forwarded-Host"].split(":") + : []; + let [, hostPortStr] = req.headers["host"]?.split(":") ?? []; let hostnamePort = Number.parseInt(hostnamePortStr, 10); let hostPort = Number.parseInt(hostPortStr, 10); let port = Number.isSafeInteger(hostnamePort) @@ -108,9 +113,13 @@ export function createRemixRequest( ? hostPort : ""; // Use req.hostname here as it respects the "trust proxy" setting - let resolvedHost = `${req.hostname}${port ? `:${port}` : ""}`; + const protocol = req.socket instanceof TLSSocket ? "https" : "http"; + const requestUrl = new URL( + `${protocol}://${req.headers.host}${req.url ?? ""}` + ); + let resolvedHost = `${requestUrl.hostname}${port ? `:${port}` : ""}`; // Use `req.originalUrl` so Remix is aware of the full path - let url = new URL(`${req.protocol}://${resolvedHost}${req.originalUrl}`); + let url = new URL(`${protocol}://${resolvedHost}${req.url}`); // Abort action/loaders once we can no longer write a response let controller: AbortController | null = new AbortController(); @@ -136,14 +145,14 @@ export function createRemixRequest( } export async function sendRemixResponse( - res: express.Response, + res: ServerResponse, nodeResponse: Response ): Promise { res.statusMessage = nodeResponse.statusText; - res.status(nodeResponse.status); + res.statusCode = nodeResponse.status; for (let [key, value] of nodeResponse.headers.entries()) { - res.append(key, value); + res.appendHeader(key, value); } if (nodeResponse.headers.get("Content-Type")?.match(/text\/event-stream/i)) {