Skip to content

Commit e7e6a1f

Browse files
committed
refactor: proxy/preview server
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 a979d55 commit e7e6a1f

File tree

3 files changed

+303
-161
lines changed

3 files changed

+303
-161
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 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. 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
@@ -23,7 +23,7 @@ import { getAPIToken } from "./user";
2323
import fetch from "node-fetch";
2424
import makeModuleCollector from "./module-collection";
2525
import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
26-
import { createHttpProxy } from "./proxy";
26+
import { usePreviewServer } from "./proxy";
2727
import { execa } from "execa";
2828
import { watch } from "chokidar";
2929

@@ -39,7 +39,7 @@ export type DevProps = {
3939
jsxFactory: void | string;
4040
jsxFragment: void | string;
4141
bindings: CfWorkerInit["bindings"];
42-
public: void | string;
42+
public: undefined | string;
4343
site: void | string;
4444
compatibilityDate: void | string;
4545
compatibilityFlags: void | string[];
@@ -167,7 +167,7 @@ function Remote(props: {
167167
name: void | string;
168168
bundle: EsbuildBundle | void;
169169
format: CfScriptFormat;
170-
public: void | string;
170+
public: undefined | string;
171171
site: void | string;
172172
port: number;
173173
accountId: void | string;
@@ -179,7 +179,7 @@ function Remote(props: {
179179
}) {
180180
assert(props.accountId, "accountId is required");
181181
assert(props.apiToken, "apiToken is required");
182-
const token = useWorker({
182+
const previewToken = useWorker({
183183
name: props.name,
184184
bundle: props.bundle,
185185
format: props.format,
@@ -194,9 +194,13 @@ function Remote(props: {
194194
usageModel: props.usageModel,
195195
});
196196

197-
useProxy({ token, publicRoot: props.public, port: props.port });
197+
usePreviewServer({
198+
previewToken,
199+
publicRoot: props.public,
200+
port: props.port,
201+
});
198202

199-
useInspector(token ? token.inspectorUrl.href : undefined);
203+
useInspector(previewToken ? previewToken.inspectorUrl.href : undefined);
200204
return null;
201205
}
202206
function Local(props: {
@@ -539,7 +543,7 @@ function useWorker(props: {
539543
compatibilityDate: string | void;
540544
compatibilityFlags: string[] | void;
541545
usageModel: void | "bundled" | "unbound";
542-
}): CfPreviewToken | void {
546+
}): CfPreviewToken | undefined {
543547
const {
544548
name,
545549
bundle,
@@ -554,7 +558,7 @@ function useWorker(props: {
554558
usageModel,
555559
port,
556560
} = props;
557-
const [token, setToken] = useState<CfPreviewToken>();
561+
const [token, setToken] = useState<CfPreviewToken | undefined>();
558562

559563
// This is the most reliable way to detect whether
560564
// something's "happened" in our system; We make a ref and
@@ -563,6 +567,8 @@ function useWorker(props: {
563567

564568
useEffect(() => {
565569
async function start() {
570+
setToken(undefined); // reset token in case we're re-running
571+
566572
if (!bundle) return;
567573
if (format === "modules" && bundle.type === "commonjs") {
568574
console.error("⎔ Cannot use modules with a commonjs bundle.");
@@ -576,7 +582,6 @@ function useWorker(props: {
576582
}
577583

578584
if (!startedRef.current) {
579-
console.log("⎔ Starting server...");
580585
startedRef.current = true;
581586
} else {
582587
console.log("⎔ Detected changes, restarting server...");
@@ -654,72 +659,6 @@ function useWorker(props: {
654659
return token;
655660
}
656661

657-
function useProxy({
658-
token,
659-
publicRoot,
660-
port,
661-
}: {
662-
token: CfPreviewToken | void;
663-
publicRoot: void | string;
664-
port: number;
665-
}) {
666-
useEffect(() => {
667-
if (!token) return;
668-
// TODO(soon): since headers are added in callbacks, the server
669-
// does not need to restart when changes are made.
670-
const host = token.host;
671-
const proxy = createHttpProxy({
672-
host,
673-
assetPath: typeof publicRoot === "string" ? publicRoot : null,
674-
onRequest: (headers) => {
675-
headers["cf-workers-preview-token"] = token.value;
676-
},
677-
onResponse: (headers) => {
678-
for (const [name, value] of Object.entries(headers)) {
679-
// Rewrite the remote host to the local host.
680-
if (typeof value === "string" && value.includes(host)) {
681-
headers[name] = value
682-
.replaceAll(`https://${host}`, `http://localhost:${port}`)
683-
.replaceAll(host, `localhost:${port}`);
684-
}
685-
}
686-
},
687-
});
688-
689-
console.log(`⬣ Listening at http://localhost:${port}`);
690-
691-
const server = proxy.listen(port);
692-
693-
// TODO(soon): refactor logging format into its own function
694-
proxy.on("request", function (req, res) {
695-
// log all requests
696-
console.log(
697-
new Date().toLocaleTimeString(),
698-
req.method,
699-
req.url,
700-
res.statusCode
701-
);
702-
});
703-
proxy.on("upgrade", (req) => {
704-
console.log(
705-
new Date().toLocaleTimeString(),
706-
req.method,
707-
req.url,
708-
101,
709-
"(WebSocket)"
710-
);
711-
});
712-
proxy.on("error", (err) => {
713-
console.error(new Date().toLocaleTimeString(), err);
714-
});
715-
716-
return () => {
717-
proxy.close();
718-
server.close();
719-
};
720-
}, [token, publicRoot, port]);
721-
}
722-
723662
function useInspector(inspectorUrl: string | void) {
724663
useEffect(() => {
725664
if (!inspectorUrl) return;

0 commit comments

Comments
 (0)