Skip to content

Commit

Permalink
Request forwarding (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanto authored Sep 30, 2024
1 parent 9930325 commit 01bfefb
Show file tree
Hide file tree
Showing 26 changed files with 187 additions and 53 deletions.
7 changes: 7 additions & 0 deletions examples/base-app/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# base-app

## 0.0.27

### Patch Changes

- Updated dependencies
- @twofold/framework@0.0.28

## 0.0.26

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion examples/base-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "base-app",
"version": "0.0.26",
"version": "0.0.27",
"private": true,
"description": "",
"type": "module",
Expand Down
7 changes: 7 additions & 0 deletions examples/databases/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# databases

## 0.0.21

### Patch Changes

- Updated dependencies
- @twofold/framework@0.0.28

## 0.0.20

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion examples/databases/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "databases",
"version": "0.0.20",
"version": "0.0.21",
"private": true,
"description": "",
"type": "module",
Expand Down
7 changes: 7 additions & 0 deletions examples/kitchen-sink/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# kitchen-sink

## 1.0.27

### Patch Changes

- Updated dependencies
- @twofold/framework@0.0.28

## 1.0.26

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion examples/kitchen-sink/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kitchen-sink",
"version": "1.0.26",
"version": "1.0.27",
"private": true,
"description": "",
"type": "module",
Expand Down
40 changes: 40 additions & 0 deletions examples/kitchen-sink/src/pages/http/request-forwarding.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { PageProps } from "@twofold/framework/types";

export default function RequestForwardingPage({ request }: PageProps) {
let url = new URL(request.url);
let headers = request.headers;

return (
<div>
<h1 className="text-4xl font-extrabold tracking-tighter">
Request forwarding
</h1>
<p className="pt-3 text-gray-800">
Requests passed to pages from proxies and load balancers will have the
correct url, host, and protocol.
</p>
<div className="space-y-4 pt-4">
<div>
<div className="text-sm text-gray-500">Request URL</div>
<div>{request.url}</div>
</div>
<div>
<div className="text-sm text-gray-500">Forwarded host</div>
<div>{headers.get("x-forwarded-host") ?? "None"}</div>
</div>
<div>
<div className="text-sm text-gray-500">URL host</div>
<div>{url.host}</div>
</div>
<div>
<div className="text-sm text-gray-500">Forwarded protocol</div>
<div>{headers.get("x-forwarded-proto") ?? "None"}</div>
</div>
<div>
<div className="text-sm text-gray-500">URL protocol</div>
<div>{url.protocol}</div>
</div>
</div>
</div>
);
}
5 changes: 5 additions & 0 deletions examples/kitchen-sink/src/pages/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ export default function Nav() {
description="API for lower-level access to the server."
href="/http/api"
/>
<ExampleLink
title="Request forwarding"
description="Request normalization for proxies and load balancers."
href="/http/request-forwarding"
/>
</ExampleGroup>

<ExampleGroup name="Build">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { notFound } from "@twofold/framework/not-found";

export async function before() {
await new Promise((resolve) => setTimeout(resolve, 1000));
notFound();
}

export default function Page() {
return <div>You should not see this!</div>;
}
8 changes: 8 additions & 0 deletions examples/kitchen-sink/src/pages/routing/not-found/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ export default function Layout({ children }: { children: ReactNode }) {
Middleware calls notFound
</Link>
</li>
<li>
<Link
href="/routing/not-found/async-middleware-not-found"
className="text-blue-500 underline"
>
Async middleware notFound
</Link>
</li>
<li>
<Link
href="/routing/not-found/action-calls-not-found"
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin-twofold/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# eslint-plugin-twofold

## 0.0.28

## 0.0.27

## 0.0.26
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-twofold/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-twofold",
"version": "0.0.27",
"version": "0.0.28",
"description": "",
"main": "index.js",
"scripts": {
Expand Down
6 changes: 6 additions & 0 deletions packages/framework/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @twofold/framework

## 0.0.28

### Patch Changes

- expose a render api

## 0.0.27

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/framework/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@twofold/framework",
"version": "0.0.27",
"version": "0.0.28",
"description": "",
"type": "module",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions packages/framework/src/backend/build/externals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const externalPackages = [
"bcrypt",
"better-sqlite3",
"canvas",
"cheerio",
"cpu-features",
"cypress",
"dd-trace",
Expand Down
2 changes: 1 addition & 1 deletion packages/framework/src/backend/build/rsc/rsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class RSC {
}) {
let module = await this.loadModule();
if (module.before) {
module.before(props);
await module.before(props);
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/framework/src/backend/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ export class Runtime {
this.#ssrWorker = undefined;
}

// only create if the build is done
// emit some event with ssr is ready

if (!this.#build.error) {
let bootstrapUrl = `/_assets/client-app/bootstrap/${this.#build.getBuilder("client").bootstrapHash}.js`;
let workerUrl = new URL("./ssr/worker.js", import.meta.url);
Expand Down
4 changes: 3 additions & 1 deletion packages/framework/src/backend/runtime/api-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
isRedirectError,
redirectErrorInfo,
} from "./helpers/errors.js";
import { forwardedRequest } from "./helpers/request.js";

export class APIRequest {
#api: API;
Expand Down Expand Up @@ -70,11 +71,12 @@ export class APIRequest {
let execPattern = this.#api.pattern.exec(url);
let params = execPattern?.pathname.groups ?? {};
let searchParams = url.searchParams;
let request = forwardedRequest(this.#request);

return {
params,
searchParams,
request: this.#request,
request,
};
}
}
15 changes: 15 additions & 0 deletions packages/framework/src/backend/runtime/helpers/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function forwardedRequest(request: Request) {
let headers = request.headers;

if (!headers.has("x-forwarded-proto") && !headers.has("x-forwarded-host")) {
return request;
}

let originalUrl = new URL(request.url);
originalUrl.protocol =
request.headers.get("x-forwarded-proto") || originalUrl.protocol;
originalUrl.host =
request.headers.get("x-forwarded-host") || originalUrl.host;

return new Request(originalUrl.toString(), request);
}
8 changes: 5 additions & 3 deletions packages/framework/src/backend/runtime/page-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
isRedirectError,
redirectErrorInfo,
} from "./helpers/errors.js";
import { forwardedRequest } from "./helpers/request.js";

export class PageRequest {
#page: Page;
Expand Down Expand Up @@ -125,15 +126,16 @@ export class PageRequest {
let execPattern = this.#page.pattern.exec(url);
let params = execPattern?.pathname.groups ?? {};
let searchParams = url.searchParams;
let request = forwardedRequest(this.#request);

return {
params,
searchParams,
request: this.#request,
request,
};
}

private async runMiddleware() {
private runMiddleware() {
let rsc = this.#page.rsc;
let layouts = this.#page.layouts;
let props = this.props;
Expand All @@ -143,7 +145,7 @@ export class PageRequest {
...layouts.map((layout) => layout.rsc.runMiddleware(props)),
];

await Promise.all(promises);
return Promise.all(promises);
}

private notFoundRscResponse() {
Expand Down
5 changes: 5 additions & 0 deletions packages/framework/src/backend/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { pathNormalization } from "./server/middlewares/path-normalization.js";
import { session } from "./server/middlewares/session.js";
import { globalMiddleware } from "./server/middlewares/global-middleware.js";
import { requestStore } from "./server/middlewares/request-store.js";
import { waitForBuild } from "./server/middlewares/wait-for-build.js";
import {
isNotFoundError,
isRedirectError,
Expand All @@ -34,6 +35,10 @@ export async function create(runtime: Runtime) {
app.use(cookie());
app.use(await session());

if (build.env === "development") {
app.use(waitForBuild(build));
}

app.use(globalMiddleware(build));
app.use(assets(build));
app.use(staticFiles(build));
Expand Down
74 changes: 35 additions & 39 deletions packages/framework/src/backend/server/middlewares/dev-reload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,40 @@ type Connection = {
let activeConnections: Connection[] = [];

export function devReload(build: DevBuild): RouteHandler {
if (process.env.NODE_ENV === "production") {
return () => {};
} else {
return async ({ request }) => {
let url = new URL(request.url);
let pathname = url.pathname;
let method = request.method;

if (method === "GET" && pathname === "/__dev/reload") {
let handler: () => void;

return serverSentEvents({
onOpen(sink) {
handler = () => {
// console.log(build.changes);
sink.sendMessage(JSON.stringify(build.changes));
};

let close = () => {
sink.close();
build.events.off("complete", handler);
};

if (activeConnections.length > 8) {
let connection = activeConnections.shift();
if (connection) {
connection.close();
}
}

activeConnections.push({ close, sink });
build.events.on("complete", handler);
},
onClose() {
return async ({ request }) => {
let url = new URL(request.url);
let pathname = url.pathname;
let method = request.method;

if (method === "GET" && pathname === "/__dev/reload") {
let handler: () => void;

return serverSentEvents({
onOpen(sink) {
handler = () => {
// console.log(build.changes);
sink.sendMessage(JSON.stringify(build.changes));
};

let close = () => {
sink.close();
build.events.off("complete", handler);
},
});
}
};
}
};

if (activeConnections.length > 8) {
let connection = activeConnections.shift();
if (connection) {
connection.close();
}
}

activeConnections.push({ close, sink });
build.events.on("complete", handler);
},
onClose() {
build.events.off("complete", handler);
},
});
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { RouteHandler } from "@hattip/router";
import { DevBuild } from "../../build/dev-build";

export function waitForBuild(build: DevBuild): RouteHandler {
return async () => {
if (build.isBuilding) {
await new Promise<void>((resolve) => {
build.events.once("complete", () => {
resolve();
});
});
}
};
}
Loading

0 comments on commit 01bfefb

Please sign in to comment.