Skip to content

Commit 18f8f65

Browse files
refactor: proxy/preview server (#231)
Closes #205 This PR refactors how we setup the proxy server between the developer and the edge preview service during `wrangler dev`. Of note, we start the server immediately. We also buffers requests/streams and hold on to the, when starting/refreshing the token. This means a developer should never see `ERR_CONNECTION_REFUSED` error page, or have an older worker respond after making a change to the code. When the token does get refreshed, we flush said streams/requests with the newer values.
1 parent f0a2b68 commit 18f8f65

File tree

3 files changed

+288
-151
lines changed

3 files changed

+288
-151
lines changed

.changeset/angry-schools-walk.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
refactor: proxy/preview server
6+
7+
This PR refactors how we setup the proxy server between the developer and the edge preview service during `wrangler dev`. Of note, we start the server immediately. We also buffer requests/streams and hold on to them, when starting/refreshing the token. This means a developer should never see `ERR_CONNECTION_REFUSED` error page, or have an older worker respond after making a change to the code. And when the token does get refreshed, we flush said streams/requests with the newer values, making the iteration process a lot smoother and predictable.

packages/wrangler/src/dev.tsx

+14-75
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { getAPIToken } from "./user";
2222
import fetch from "node-fetch";
2323
import makeModuleCollector from "./module-collection";
2424
import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
25-
import { createHttpProxy } from "./proxy";
25+
import { usePreviewServer } from "./proxy";
2626
import { execa } from "execa";
2727
import { watch } from "chokidar";
2828

@@ -38,7 +38,7 @@ export type DevProps = {
3838
jsxFactory: void | string;
3939
jsxFragment: void | string;
4040
bindings: CfWorkerInit["bindings"];
41-
public: void | string;
41+
public: undefined | string;
4242
site: void | string;
4343
compatibilityDate: void | string;
4444
compatibilityFlags: void | string[];
@@ -133,7 +133,7 @@ function Remote(props: {
133133
name: void | string;
134134
bundle: EsbuildBundle | void;
135135
format: CfScriptFormat;
136-
public: void | string;
136+
public: undefined | string;
137137
site: void | string;
138138
port: number;
139139
accountId: void | string;
@@ -145,7 +145,7 @@ function Remote(props: {
145145
}) {
146146
assert(props.accountId, "accountId is required");
147147
assert(props.apiToken, "apiToken is required");
148-
const token = useWorker({
148+
const previewToken = useWorker({
149149
name: props.name,
150150
bundle: props.bundle,
151151
format: props.format,
@@ -160,10 +160,14 @@ function Remote(props: {
160160
usageModel: props.usageModel,
161161
});
162162

163-
useProxy({ token, publicRoot: props.public, port: props.port });
163+
usePreviewServer({
164+
previewToken,
165+
publicRoot: props.public,
166+
port: props.port,
167+
});
164168

165169
useInspector({
166-
inspectorUrl: token ? token.inspectorUrl.href : undefined,
170+
inspectorUrl: previewToken ? previewToken.inspectorUrl.href : undefined,
167171
port: 9229,
168172
logToTerminal: true,
169173
});
@@ -509,7 +513,7 @@ function useWorker(props: {
509513
compatibilityDate: string | void;
510514
compatibilityFlags: string[] | void;
511515
usageModel: void | "bundled" | "unbound";
512-
}): CfPreviewToken | void {
516+
}): CfPreviewToken | undefined {
513517
const {
514518
name,
515519
bundle,
@@ -524,7 +528,7 @@ function useWorker(props: {
524528
usageModel,
525529
port,
526530
} = props;
527-
const [token, setToken] = useState<CfPreviewToken>();
531+
const [token, setToken] = useState<CfPreviewToken | undefined>();
528532

529533
// This is the most reliable way to detect whether
530534
// something's "happened" in our system; We make a ref and
@@ -533,6 +537,8 @@ function useWorker(props: {
533537

534538
useEffect(() => {
535539
async function start() {
540+
setToken(undefined); // reset token in case we're re-running
541+
536542
if (!bundle) return;
537543
if (format === "modules" && bundle.type === "commonjs") {
538544
console.error("⎔ Cannot use modules with a commonjs bundle.");
@@ -546,7 +552,6 @@ function useWorker(props: {
546552
}
547553

548554
if (!startedRef.current) {
549-
console.log("⎔ Starting server...");
550555
startedRef.current = true;
551556
} else {
552557
console.log("⎔ Detected changes, restarting server...");
@@ -624,72 +629,6 @@ function useWorker(props: {
624629
return token;
625630
}
626631

627-
function useProxy({
628-
token,
629-
publicRoot,
630-
port,
631-
}: {
632-
token: CfPreviewToken | void;
633-
publicRoot: void | string;
634-
port: number;
635-
}) {
636-
useEffect(() => {
637-
if (!token) return;
638-
// TODO(soon): since headers are added in callbacks, the server
639-
// does not need to restart when changes are made.
640-
const host = token.host;
641-
const proxy = createHttpProxy({
642-
host,
643-
assetPath: typeof publicRoot === "string" ? publicRoot : null,
644-
onRequest: (headers) => {
645-
headers["cf-workers-preview-token"] = token.value;
646-
},
647-
onResponse: (headers) => {
648-
for (const [name, value] of Object.entries(headers)) {
649-
// Rewrite the remote host to the local host.
650-
if (typeof value === "string" && value.includes(host)) {
651-
headers[name] = value
652-
.replaceAll(`https://${host}`, `http://localhost:${port}`)
653-
.replaceAll(host, `localhost:${port}`);
654-
}
655-
}
656-
},
657-
});
658-
659-
console.log(`⬣ Listening at http://localhost:${port}`);
660-
661-
const server = proxy.listen(port);
662-
663-
// TODO(soon): refactor logging format into its own function
664-
proxy.on("request", function (req, res) {
665-
// log all requests
666-
console.log(
667-
new Date().toLocaleTimeString(),
668-
req.method,
669-
req.url,
670-
res.statusCode
671-
);
672-
});
673-
proxy.on("upgrade", (req) => {
674-
console.log(
675-
new Date().toLocaleTimeString(),
676-
req.method,
677-
req.url,
678-
101,
679-
"(WebSocket)"
680-
);
681-
});
682-
proxy.on("error", (err) => {
683-
console.error(new Date().toLocaleTimeString(), err);
684-
});
685-
686-
return () => {
687-
proxy.close();
688-
server.close();
689-
};
690-
}, [token, publicRoot, port]);
691-
}
692-
693632
function sleep(period: number) {
694633
return new Promise((resolve) => setTimeout(resolve, period));
695634
}

0 commit comments

Comments
 (0)