From 48f5e83d42f4d330b02aa11f09222586eef70deb Mon Sep 17 00:00:00 2001 From: conico974 Date: Thu, 2 Nov 2023 17:21:48 +0100 Subject: [PATCH] Fix external rewrite (#299) * fix rewrites not working * fix middleware response next 14 --- .../plugins/routing/default.replacement.ts | 19 ++++---- .../src/adapters/plugins/routing/util.ts | 19 +++++++- .../src/adapters/routing/middleware.ts | 44 ++++++++++++++----- .../open-next/src/adapters/routing/util.ts | 5 ++- 4 files changed, 63 insertions(+), 24 deletions(-) diff --git a/packages/open-next/src/adapters/plugins/routing/default.replacement.ts b/packages/open-next/src/adapters/plugins/routing/default.replacement.ts index 8a21440c..3388d1f0 100644 --- a/packages/open-next/src/adapters/plugins/routing/default.replacement.ts +++ b/packages/open-next/src/adapters/plugins/routing/default.replacement.ts @@ -59,15 +59,16 @@ export const processInternalEvent: ProcessInternalEvent = async ( internalEvent = middleware; } - let isExternalRewrite = false; - // First rewrite to be applied - const beforeRewrites = handleRewrites( - internalEvent, - RoutesManifest.rewrites.beforeFiles, - ); - internalEvent = beforeRewrites.internalEvent; - isExternalRewrite = beforeRewrites.isExternalRewrite; - + let isExternalRewrite = middleware.externalRewrite ?? false; + if (!isExternalRewrite) { + // First rewrite to be applied + const beforeRewrites = handleRewrites( + internalEvent, + RoutesManifest.rewrites.beforeFiles, + ); + internalEvent = beforeRewrites.internalEvent; + isExternalRewrite = beforeRewrites.isExternalRewrite; + } const isStaticRoute = RoutesManifest.routes.static.some((route) => new RegExp(route.regex).test(event.rawPath), ); diff --git a/packages/open-next/src/adapters/plugins/routing/util.ts b/packages/open-next/src/adapters/plugins/routing/util.ts index 96044dce..57f7522e 100644 --- a/packages/open-next/src/adapters/plugins/routing/util.ts +++ b/packages/open-next/src/adapters/plugins/routing/util.ts @@ -1,5 +1,6 @@ import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs"; import crypto from "crypto"; +import { ServerResponse } from "http"; import { BuildId, HtmlPages } from "../../config/index.js"; import { IncomingMessage } from "../../http/request.js"; @@ -36,14 +37,28 @@ export async function proxyRequest( }); await new Promise((resolve, reject) => { - proxy.on("proxyRes", () => { - resolve(); + proxy.on("proxyRes", (proxyRes: ServerResponse) => { + const body: Uint8Array[] = []; + proxyRes.on("data", function (chunk) { + body.push(chunk); + }); + proxyRes.on("end", function () { + const newBody = Buffer.concat(body).toString(); + debug(`Proxying response`, { + headers: proxyRes.getHeaders(), + body: newBody, + }); + res.end(newBody); + resolve(); + }); }); proxy.on("error", (err: any) => { reject(err); }); + debug(`Proxying`, { url: req.url, headers: req.headers }); + proxy.web(req, res, { target: req.url, headers: req.headers, diff --git a/packages/open-next/src/adapters/routing/middleware.ts b/packages/open-next/src/adapters/routing/middleware.ts index 34921c0f..2faa1122 100644 --- a/packages/open-next/src/adapters/routing/middleware.ts +++ b/packages/open-next/src/adapters/routing/middleware.ts @@ -7,13 +7,15 @@ import { ServerlessResponse } from "../http/response.js"; import { convertRes, getMiddlewareMatch, + isExternal, loadMiddlewareManifest, } from "./util.js"; const middlewareManifest = loadMiddlewareManifest(NEXT_DIR); +//NOTE: we should try to avoid importing stuff from next as much as possible +// every release of next could break this const { run } = require("next/dist/server/web/sandbox"); -const { pipeReadable } = require("next/dist/server/pipe-readable"); const { getCloneableBody } = require("next/dist/server/body-streams"); const { signalFromNodeResponse, @@ -23,6 +25,7 @@ const middleMatch = getMiddlewareMatch(middlewareManifest); type MiddlewareOutputEvent = InternalEvent & { responseHeaders?: Record; + externalRewrite?: boolean; }; // NOTE: As of Nextjs 13.4.13+, the middleware is handled outside the next-server. @@ -30,6 +33,10 @@ type MiddlewareOutputEvent = InternalEvent & { // and res.body prior to processing the next-server. // @returns undefined | res.end() +interface MiddlewareResult { + response: Response; +} + // if res.end() is return, the parent needs to return and not process next server export async function handleMiddleware( internalEvent: InternalEvent, @@ -65,7 +72,7 @@ export async function handleMiddleware( initialUrl.search = new URLSearchParams(urlQuery).toString(); const url = initialUrl.toString(); - const result = await run({ + const result: MiddlewareResult = await run({ distDir: NEXT_DIR, name: middlewareInfo.name || "/", paths: middlewareInfo.paths || [], @@ -129,7 +136,7 @@ export async function handleMiddleware( statusCode: res.statusCode, headers: { ...resHeaders, - Location: location, + Location: location ?? "", }, isBase64Encoded: false, }; @@ -139,22 +146,34 @@ export async function handleMiddleware( // NOTE: the header was added to `req` from above const rewriteUrl = responseHeaders.get("x-middleware-rewrite"); let rewritten = false; + let externalRewrite = false; let middlewareQueryString = internalEvent.query; if (rewriteUrl) { - const rewriteUrlObject = new URL(rewriteUrl); - req.url = rewriteUrlObject.pathname; - //reset qs - middlewareQueryString = {}; - rewriteUrlObject.searchParams.forEach((v: string, k: string) => { - middlewareQueryString[k] = v; - }); - rewritten = true; + if (isExternal(rewriteUrl, req.headers.host)) { + req.url = rewriteUrl; + rewritten = true; + externalRewrite = true; + } else { + const rewriteUrlObject = new URL(rewriteUrl); + req.url = rewriteUrlObject.pathname; + //reset qs + middlewareQueryString = {}; + rewriteUrlObject.searchParams.forEach((v: string, k: string) => { + middlewareQueryString[k] = v; + }); + rewritten = true; + } } // If the middleware returned a `NextResponse`, pipe the body to res. This will return // the body immediately to the client. if (result.response.body) { - await pipeReadable(result.response.body, res); + // transfer response body to res + const arrayBuffer = await result.response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + res.end(buffer); + + // await pipeReadable(result.response.body, res); return { type: internalEvent.type, ...convertRes(res), @@ -174,5 +193,6 @@ export async function handleMiddleware( query: middlewareQueryString, cookies: internalEvent.cookies, remoteAddress: internalEvent.remoteAddress, + externalRewrite, }; } diff --git a/packages/open-next/src/adapters/routing/util.ts b/packages/open-next/src/adapters/routing/util.ts index 21fbd5d9..eeef3cee 100644 --- a/packages/open-next/src/adapters/routing/util.ts +++ b/packages/open-next/src/adapters/routing/util.ts @@ -5,9 +5,12 @@ import { isBinaryContentType } from "../binary"; import { ServerlessResponse } from "../http/response"; import { MiddlewareManifest } from "../types/next-types"; -export function isExternal(url?: string) { +export function isExternal(url?: string, host?: string) { if (!url) return false; const pattern = /^https?:\/\//; + if (host) { + return pattern.test(url) && !url.includes(host); + } return pattern.test(url); }