Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 1416b1d

Browse files
authoredMay 15, 2023
Allow custom upstreams to be specified (#568)
This adds back Miniflare 2's `upstream` option, which allows you to customise the origin of incoming requests to the Worker. This is useful when implementing proxy servers with Workers. Wrangler exposes this option as the `--local-upstream` flag, so we'd like to support this in Miniflare 3 to avoid regressing when we graduate. Closes cloudflare/workers-sdk#2821 Closes DEVX-628

File tree

4 files changed

+40
-4
lines changed

4 files changed

+40
-4
lines changed
 

‎packages/tre/src/plugins/core/index.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export const CoreSharedOptionsSchema = z.object({
7171
log: z.instanceof(Log).optional(),
7272
timers: z.custom<Timers>().optional(),
7373

74+
upstream: z.string().optional(),
7475
// TODO: add back validation of cf object
7576
cf: z.union([z.boolean(), z.string(), z.record(z.any())]).optional(),
7677

@@ -345,6 +346,12 @@ export function getGlobalServices({
345346
service: { name: getUserServiceName(name) },
346347
})),
347348
];
349+
if (sharedOptions.upstream !== undefined) {
350+
serviceEntryBindings.push({
351+
name: CoreBindings.TEXT_UPSTREAM_URL,
352+
text: sharedOptions.upstream,
353+
});
354+
}
348355
if (sharedOptions.liveReload) {
349356
const liveReloadScript = LIVE_RELOAD_SCRIPT_TEMPLATE(loopbackPort);
350357
serviceEntryBindings.push({
@@ -371,9 +378,7 @@ export function getGlobalServices({
371378
{
372379
name: "internet",
373380
network: {
374-
// Can't use `["public", "private"]` here because of
375-
// https://github.com/cloudflare/workerd/issues/62
376-
allow: ["0.0.0.0/0"],
381+
allow: ["public", "private"],
377382
deny: [],
378383
tlsOptions: { trustBrowserCas: true },
379384
},

‎packages/tre/src/workers/core/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const CoreBindings = {
1111
SERVICE_USER_ROUTE_PREFIX: "MINIFLARE_USER_ROUTE_",
1212
SERVICE_USER_FALLBACK: "MINIFLARE_USER_FALLBACK",
1313
TEXT_CUSTOM_SERVICE: "MINIFLARE_CUSTOM_SERVICE",
14+
TEXT_UPSTREAM_URL: "MINIFLARE_UPSTREAM_URL",
1415
JSON_CF_BLOB: "CF_BLOB",
1516
JSON_ROUTES: "MINIFLARE_ROUTES",
1617
JSON_LOG_LEVEL: "MINIFLARE_LOG_LEVEL",

‎packages/tre/src/workers/core/entry.worker.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type Env = {
1111
[CoreBindings.SERVICE_LOOPBACK]: Fetcher;
1212
[CoreBindings.SERVICE_USER_FALLBACK]: Fetcher;
1313
[CoreBindings.TEXT_CUSTOM_SERVICE]: string;
14+
[CoreBindings.TEXT_UPSTREAM_URL]?: string;
1415
[CoreBindings.JSON_CF_BLOB]: IncomingRequestCfProperties;
1516
[CoreBindings.JSON_ROUTES]: WorkerRoute[];
1617
[CoreBindings.JSON_LOG_LEVEL]: LogLevel;
@@ -26,7 +27,17 @@ function getUserRequest(
2627
env: Env
2728
) {
2829
const originalUrl = request.headers.get(CoreHeaders.ORIGINAL_URL);
29-
request = new Request(originalUrl ?? request.url, {
30+
const upstreamUrl = env[CoreBindings.TEXT_UPSTREAM_URL];
31+
let url = new URL(originalUrl ?? request.url);
32+
if (upstreamUrl !== undefined) {
33+
// If a custom `upstream` was specified, make sure the URL starts with it
34+
let path = url.pathname + url.search;
35+
// Remove leading slash, so we resolve relative to `upstream`'s path
36+
if (path.startsWith("/")) path = path.substring(1);
37+
url = new URL(path, upstreamUrl);
38+
}
39+
40+
request = new Request(url, {
3041
method: request.method,
3142
headers: request.headers,
3243
cf: request.cf ?? env[CoreBindings.JSON_CF_BLOB],

‎packages/tre/test/index.spec.ts

+19
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
MessageEvent as StandardMessageEvent,
1616
WebSocketServer,
1717
} from "ws";
18+
import { useServer } from "./test-shared";
1819

1920
test("Miniflare: validates options", async (t) => {
2021
// Check empty workers array rejected
@@ -231,3 +232,21 @@ test("Miniflare: custom service binding to another Miniflare instance", async (t
231232
body: null,
232233
});
233234
});
235+
236+
test("Miniflare: uses custom upstream as origin", async (t) => {
237+
const upstream = await useServer(t, (req, res) => {
238+
res.end(`upstream: ${new URL(req.url ?? "", "http://upstream")}`);
239+
});
240+
const mf = new Miniflare({
241+
upstream: new URL("/extra/", upstream.http.toString()).toString(),
242+
modules: true,
243+
script: `export default {
244+
fetch(request) {
245+
return fetch(request);
246+
}
247+
}`,
248+
});
249+
// Check rewrites protocol, hostname, and port, but keeps pathname and query
250+
const res = await mf.dispatchFetch("https://random:0/path?a=1");
251+
t.is(await res.text(), "upstream: http://upstream/extra/path?a=1");
252+
});

0 commit comments

Comments
 (0)
This repository has been archived.