Live data from Hacker News

Cap'n Web: a new RPC system for browsers and web servers

blog.cloudflare.com

61–70 of 305 posts

Re: Cap'n Web: a new RPC system for browsers and web servers

#61
post #6

Earlier quoted context omitted.

Yeah I now want to go back and redesign the Cap'n Proto RPC protocol to be based on this new design, as it accomplishes all the same features with a lot less complexity! But it may be tough to justify when we already have working Cap'n Proto implementations speaking the existing protocol, that took a lot of work to build. Yes, the new implementations will be less work than the original, but it's still a lot of work t…

You mean redesign Cap'n Proto to not have a schema? Or did you mean the API, not the protocol?

Here is the Cap'n Proto RPC protocol:

https://github.com/capnproto/capnproto/blob/v2/c%2B%2B/src/c...

That's just the RPC state machine -- the serialization is specified elsewhere, and the state machine is actually schema-agnostic. (Schemas are applied at the edges, when messages are actually received from the app or delivered to it.)

This is the Cap'n Web protocol, including serialization details:

https://github.com/cloudflare/capnweb/blob/main/protocol.md

Now, to be fair, Cap'n Proto has a lot of features that Cap'n Web doesn't have yet. But Cap'n Web's high-level design is actually a lot simpler.

Among other things, I merged the concepts of call-return and promise-resolve. (Which, admittedly, CapTP was doing it that way before I even designed Cap'n Proto. It was a complete mistake on my part to turn them into two separate concepts in Cap'n Proto, but it seemed to make sense at the time.)

What I'd like to do is go back and revise the Cap'n Proto protocol to use a similar design under the hood. This would make no visible difference to applications (they'd still use schemas), but the state machine would be much simpler, and easier to port to more languages.

Re: Cap'n Web: a new RPC system for browsers and web servers

#62

What would this look like for other language backends to support? Eg would be neat if Rust (my webservers) could support this on the backend edit : Downvoted, is this a bad question? The title is generically "web servers", obviously the content of the post focuses primarily on TypeScript, but i'm trying to determine if there's something unique about this that means it cannot be implemented in other languages. The ser…

Now you've likely been downvoted because you're complaining about downvotes. Which is too bad because you've elicited a great answer from the author.

It's usually best to ignore downvotes. Downvoted comments are noticeably grey. If people feel that's unfair, that'll attract upvotes in my experience.

Re: Cap'n Web: a new RPC system for browsers and web servers

#63
post #38

Earlier quoted context omitted.

Yeah, JSON RPC doesn't support the pass-by-reference and lifecycle management stuff. You just have a static list of top-level functions you can call. This makes a pretty big difference in what kinds of APIs you can express. OTOH, JSON RPC is extremely simple. Cap'n Web is a relatively complicated and subtle underlying protocol.

> You just have a static list of top-level functions you can call. Actually the author of JSON RPC suggested that method names could be dynamic, there's nothing in the spec preventing that. https://groups.google.com/g/json-rpc/c/vOFAhPs_Caw/m/QYdeSp0... So you could definitely build a cursed object/reference system by packing stuff into method names if you wanted. I doubt any implementations would allow this. But yes…

Right, your methods in JSON RPC could be dynamic. JSON RPC really doesn't specify anything, so you can do anything with it. But you need conventions around that, like how does a client find out that the server exported new methods, and how does the client indicate that it is done with them? That's exactly what Cap'n Web is all about -- defining those conventions in a usable way.

Re: Cap'n Web: a new RPC system for browsers and web servers

#64
post #46

are there security issues with no schemas + callback stubs + language on the server with little typing. for example with this `hello(name)` example the server expects a string but can the client pass an callback object that is string-like and then use this to try and trick the server into doing something bad?

The protocol explicitly blocks overriding `toString()` (and all other Object.prototype members), as well as `toJSON()`, to prevent the obvious ways that you might accidentally invoke a callback when you weren't expecting to. How else might you invoke a callback by accident? That said, type checking is called out both in the blog post (in the section on TypeScript) and in the readme (under "Security Considerations").…

i was thinking if you were doing some string operations like `indexOf` then maybe that could be an issue.

Re: Cap'n Web: a new RPC system for browsers and web servers

#65
Couple random thoughts:

I'm trying to see if there's something specifically for streaming/generators. I don't think so? Of course you can use callbacks, but you have to implement your own sentinel to mark the end, and other little corner cases. It seems like you can create a callback to an anonymous function, but then the garbage collector probably can't collect that function?

---

I don't see anything about exceptions (though Error objects can be passed through).

---

Looking at array mapping: https://blog.cloudflare.com/capnweb-javascript-rpc-library/#...

I get how it works: remotePromise.map(callback) will invoke the callback to see how it behaves, then make it behave similarly on the server. But it seems awfully fragile... I am assuming something like this would fail (in this case probably silently losing the conditional):

    friendsPromise.map(friend => {friend, lastStatus: friend.isBestFriend ? api.getStatus(friend.id) : null})
---

The array escape is clever and compact: https://blog.cloudflare.com/capnweb-javascript-rpc-library/#...

---

I think the biggest question I have is: how would I apply this to my boring stateless-HTTP server? I can imagine something where there's a worker that's fairly simple and neutral that the browser connects to, and proxies to my server. But then my server can also get callbacks that it can use to connect back to the browser, and put those callbacks (capability?) into a database or something. Then it can connect to a worker (maybe?) and do server-initiated communication. But that's only good for a session. It has to be rebuilt when the browser network connection is interrupted, or if the browser page is reloaded.

I can imagine building that on top of Cap'n Web, but it feels very complicated and I can equally imagine lots of headaches.

Re: Cap'n Web: a new RPC system for browsers and web servers

#66
post #57

The section on how they solved arrays is fascinating and terrifying at the same time https://blog.cloudflare.com/capnweb-javascript-rpc-library/#... . > .map() is special. It does not send JavaScript code to the server, but it does send something like "code", restricted to a domain-specific, non-Turing-complete language. The "code" is a list of instructions that the server should carry out for each member of the arra…

I presume conditionals are banned - sort of like the rules of hooks - but how?

The input to the map function (when it is called in "record" mode on the client) is an RpcPromise for the eventual value. That means you can't actually inspect the value, you can only queue pipelined calls on it. Since you can't inspect the value, you can't do any computation or branching on it. So any computation and branching you do perform must necessarily have the same result every time the function runs, and so can simply be recorded and replayed.

The only catch is your function needs to have no side effects (other than calling RPC methods). There are a lot of systems out there that have similar restrictions.

Re: Cap'n Web: a new RPC system for browsers and web servers

#67
post #61

Earlier quoted context omitted.

You mean redesign Cap'n Proto to not have a schema? Or did you mean the API, not the protocol?

Here is the Cap'n Proto RPC protocol: https://github.com/capnproto/capnproto/blob/v2/c%2B%2B/src/c... That's just the RPC state machine -- the serialization is specified elsewhere, and the state machine is actually schema-agnostic. (Schemas are applied at the edges, when messages are actually received from the app or delivered to it.) This is the Cap'n Web protocol, including serialization details: https://github.com…

I was trying to port Cap'n Proto to modern C# as a side project when I was unemployed, since the current implementation years old and new C# features have been released that would make it much nicer to use.

I love the no-copy serialization and object capabilities, but wow, the RPC protocol is incredibly complex, it took me a while to wrap my head around it, and I often had to refer to the C++ implementation to really get it.

Re: Cap'n Web: a new RPC system for browsers and web servers

#69

Couple random thoughts: I'm trying to see if there's something specifically for streaming/generators. I don't think so? Of course you can use callbacks, but you have to implement your own sentinel to mark the end, and other little corner cases. It seems like you can create a callback to an anonymous function, but then the garbage collector probably can't collect that function? --- I don't see anything about exception…

You can define a stream like:

    interface Stream extends RpcTarget {
      write(chunk): void;
      end(): void;
      [Symbol.dispose](): void;
    }
Note that the dispose method will be called automatically when the caller disposes the stub or when they disconnect the RPC session. The `end()` method is still useful as a way to distinguish a clean end vs. an abort.

In any case, you implement this interface, and pass it over the RPC connection. The other side can now call it back to write chunks. Voila, streaming.

That said, getting flow control right is a little tricky here: if you await every `write()`, you won't fully utilize the connection, but if you don't await, you might buffer excessively. You end up wanting to count the number of bytes that aren't acknowledged yet and hold off on further writes if it goes over some threshold. Cap'n Proto actually has built-in features for this, but Cap'n Web does not (yet).

Workers RPC actually supports sending `ReadableStream` and `WritableStream` (JavaScript types) over RPC. I'd like to support that in Cap'n Web, too, but haven't gotten around to it yet. It'd basically work exactly like above, but you get to use the standard types.

---------------------

Exceptions work exactly like you'd expect. If the callee throws an exception, it is serialized, passed back to the caller, and used to reject the promise. The error also propagates to all pipelined calls that derive from the call that threw.

---------------------

The mapper function receives, as its parameter, an `RpcPromise`. So you cannot actually inspect the value, you can only pipeline on it. `friend.isBestFriend ?` won't work, because `friend.isBestFriend` will resolve as another RpcPromise (for the future property). I suppose that'll be considered truthy by JavaScript, so the branch will always evaluate true. But if you're using TypeScript, note that the type system is fully aware that `friend` is type `RpcPromise`, so hopefully that helps steer you away from doing any computation on it.

Re: Cap'n Web: a new RPC system for browsers and web servers

#70
post #66

Earlier quoted context omitted.

I presume conditionals are banned - sort of like the rules of hooks - but how?

The input to the map function (when it is called in "record" mode on the client) is an RpcPromise for the eventual value. That means you can't actually inspect the value, you can only queue pipelined calls on it. Since you can't inspect the value, you can't do any computation or branching on it. So any computation and branching you do perform must necessarily have the same result every time the function runs, and so…

Is .map specialcased or do user functions accepting callbacks work the same way? Because you could do the Scott-Mogensen thing of #ifTrue:ifFalse: if so, dualizing the control-flow decision making, offering a menu of choices/continuations.
Post reply on HN