Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC of different destinations for rpc and fetch #3402

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

penalosa
Copy link
Collaborator

TLDR: This is a quick POC of a way to point RPC calls and HTTP requests to different network addresses.

The problem

Wrangler/Miniflare uses a workerd ExternalServer to represent service bindings between Workers in local development. This is because each Worker runs in a different workerd process configured by wrangler dev (ignoring the experimental multiworker support we've been landing recently). The caller will configure:

external: {
	address: address,
	http: {
		style: HttpOptions_Style.PROXY,
	},
},

as the service binding, where address is a direct address to a capnpConnectHost configured Socket from the callee.

This means that all communication over that service binding will go to the service linked to the capnpConnectHost Socket on the callee workerd.

However, for Workers Assets this presents a problem. Communication over service bindings needs to go to a different Worker depending on whether it's RPC or HTTP (to the user worker and router worker respectively). We need a way to connect to different addresses (Miniflare exposes a Socket for every configured worker) based on the type of communication.

The proposed solution

This PR implements (with bugs, caveats, and build failures) a proposed solution which adds an additional config property to ExternalServer, addressFetch. When workerd is making a connection to an ExternalServer, it should use the address specified in addressFetch for HTTP communication and the address specified in address for RPC communication. If addressFetch is not specified it should use address for both RPC and HTTP communication.

Wrangler/Miniflare can then leverage this by pointing address at the user worker's Socket, and addressFetch at the router worker's Socket

Co-authored-by: Carmen Popoviciu <CarmenPopoviciu@users.noreply.github.com>
Copy link

The generated output of @cloudflare/workers-types has been changed by this PR. If this is intentional, run bazel build //types && rm -rf types/generated-snapshot && cp -r bazel-bin/types/definitions types/generated-snapshot to update the snapshot. Alternatively, you can download the full generated types:

Full Type Diff

@@ -663,6 +663,8 @@ struct ExternalServer {
# `HttpOptions`.

address @0 :Text;

addressFetch @7 :Text;
Copy link
Collaborator

Choose a reason for hiding this comment

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

comment required, and I think you moved the comment of the above field?

@danlapid
Copy link
Collaborator

We'll need a test as well

@danlapid
Copy link
Collaborator

Can't we do the differentiation in the proxy worker instead?

@penalosa
Copy link
Collaborator Author

penalosa commented Jan 23, 2025

Can't we do the differentiation in the proxy worker instead?

I'm guessing this means Wrangler's proxy worker? As far as I know, there's no way to forward on RPC requests from a worker. The proxy worker would have to have logic along the lines of "if HTTP send to worker a, if RPC send to worker b", but I don't know where that could be written. It couldn't go in fetch() {} since that would miss RPC methods, and I don't think there's a good way to define a catchall RPC method (AFAIK it's based on the methods available on the prototype, so exporting a Proxy as a WorkerEntrypoint wouldn't work)

@danlapid
Copy link
Collaborator

Can't we do the differentiation in the proxy worker instead?

I'm guessing this means Wrangler's proxy worker? As far as I know, there's no way to forward on RPC requests from a worker. The proxy worker would have to have logic along the lines of "if HTTP send to worker a, if RPC send to worker b", but I don't know where that could be written. It couldn't go in fetch() {} since that would miss RPC methods, and I don't think there's a good way to define a catchall RPC method (AFAIK it's based on the methods available on the prototype, so exporting a Proxy as a WorkerEntrypoint wouldn't work)

Are you certain a proxy wouldn't work?
I think you would only need to define the entry point and a proxy might work
I remember taking to Rahul about it and he seemed to believe it's possible

@penalosa
Copy link
Collaborator Author

@danlapid Fairly sure—I think this codepath would fail:

auto protoObj = JSG_REQUIRE_NONNULL(proto.tryCast<jsg::JsObject>(),
TypeError, "Exported entrypoint class's prototype chain does not end in Object.");
if (protoObj == prototypeOfObject) {
// Reached the prototype for `Object`. Stop here.
break;
}
// Awkwardly, the prototype's members are not typically enumerable, so we have to
// enumerate them rather directly.
jsg::JsArray properties = protoObj.getPropertyNames(
js, jsg::KeyCollectionFilter::OWN_ONLY,
jsg::PropertyFilter::SKIP_SYMBOLS,
jsg::IndexFilter::SKIP_INDICES);
for (auto i: kj::zeroTo(properties.size())) {
auto name = properties.get(js, i).toString(js);
if (name == "constructor"_kj) {
// Don't treat special method `constructor` as an exported handler.
continue;
}
// Only report each method name once, even if it overrides a method in a superclass.
bool isNew = true;
kj::StringPtr namePtr = seenNames.upsert(kj::mv(name), [&](auto&, auto&&) {
isNew = false;
});
if (isNew) {
errorReporter.addHandler(entrypointName, namePtr);
}
}
proto = protoObj.getPrototype();

I can definitely give it a quick go though

@penalosa
Copy link
Collaborator Author

@danlapid Having tried it, I can't figure out how to make a Proxy work—not to say it can't, but I can't figure out how. Additionally, I'm not sure doing this in userland is really the right solution here—it feels like this should be a runtime concern (as it is when deployed). If we do it in userland, then anyone else who comes along and wants to build something similar to Workers Assets needs to copy a fairly convoluted proxy worker, compared to just adding a single config property.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants