Skip to content

Commit

Permalink
Fix external rewrite (#299)
Browse files Browse the repository at this point in the history
* fix rewrites not working

* fix middleware response next 14
  • Loading branch information
conico974 authored Nov 2, 2023
1 parent ea5e302 commit 48f5e83
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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),
);
Expand Down
19 changes: 17 additions & 2 deletions packages/open-next/src/adapters/plugins/routing/util.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -36,14 +37,28 @@ export async function proxyRequest(
});

await new Promise<void>((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,
Expand Down
44 changes: 32 additions & 12 deletions packages/open-next/src/adapters/routing/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -23,13 +25,18 @@ const middleMatch = getMiddlewareMatch(middlewareManifest);

type MiddlewareOutputEvent = InternalEvent & {
responseHeaders?: Record<string, string | string[]>;
externalRewrite?: boolean;
};

// NOTE: As of Nextjs 13.4.13+, the middleware is handled outside the next-server.
// OpenNext will run the middleware in a sandbox and set the appropriate req headers
// 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,
Expand Down Expand Up @@ -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 || [],
Expand Down Expand Up @@ -129,7 +136,7 @@ export async function handleMiddleware(
statusCode: res.statusCode,
headers: {
...resHeaders,
Location: location,
Location: location ?? "",
},
isBase64Encoded: false,
};
Expand All @@ -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),
Expand All @@ -174,5 +193,6 @@ export async function handleMiddleware(
query: middlewareQueryString,
cookies: internalEvent.cookies,
remoteAddress: internalEvent.remoteAddress,
externalRewrite,
};
}
5 changes: 4 additions & 1 deletion packages/open-next/src/adapters/routing/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down

1 comment on commit 48f5e83

@vercel
Copy link

@vercel vercel bot commented on 48f5e83 Nov 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

open-next – ./

open-next-sst-dev.vercel.app
open-next-git-main-sst-dev.vercel.app
open-next.vercel.app

Please sign in to comment.