HNHacker News
TopNewBestAskShowJobs

akshayshah

636 karma · joined March 16, 2012

https://akshayshah.org
submissionscomments
akshayshah··on Why does gRPC insist on trailers?
The author also posted an interesting Twitter thread a few months ago [0], on the day my coworkers and I posted here about our gRPC-compatible RPC framework [1]. I was a bit afraid to read this post, but I shouldn't have been - the author's a class act, and he never called us out explicitly. There's not much written about what the gRPC team was _thinking_ when they wrote up the protocol, and this was a nice window into how contemporaneous changes to HTTP and the fetch API shaped their approach. Given my current work, the final section ("Lessons for Designers") really hit home.

That said, I didn't follow the central argument - that you need HTTP trailers to detect incomplete protobuf messages. What's not mentioned in the blog post is that gRPC wraps every protobuf message in a 5-byte envelope, and the bulk of the envelope is devoted to specifying the length of the enclosed message. It's easy to detect prematurely terminated messages, because they don't contain the promised number of bytes. The author says, "[i]t’s not hard to imagine that trailers would be less of an issue, if the default encoding was JSON," because JSON objects are explicitly terminated by a closing } - but it seems to me that envelopes solve that problem neatly.

With incomplete message detection handled, we're left looking for some mechanism to detect streams that prematurely terminate at a message boundary. (This is more likely than you might expect, since servers often crash at message boundaries.) In practice, gRPC implementations already buffer responses to unary RPCs. It's therefore easy to use the standard HTTP Content-Length header for unary responses. This covers the vast majority of RPCs with a simple, uncontroversial approach. Streaming responses do need some trailer-like mechanism, but not to detect premature termination - as long as we're restricting ourselves to HTTP/2, cleanly terminated streams always end with a frame with the end of stream bit set. Streaming does need some trailer-like mechanism to send the details of any errors that occur mid-stream, but there's no need to use HTTP trailers. As the author hints, there's some unused space in the message envelope - we can use one bit to flag the last message in the stream and use it for the end-of-stream metadata. This is, more or less, what the gRPC-Web protocol is. (Admittedly, it's probably a bad idea to rely on _every_ HTTP server and proxy on the internet handling premature termination correctly. We need some sort of trailer-like construct anyways, and the fact that it also improves robustness is a nice extra benefit.)

So from the outside, it doesn't seem like trailers improve the robustness of most RPCs. Instead, it seems like the gRPC protocol prioritizes some abstract notion of cleanliness over simplicity in practice: by using the same wire protocol for unary and streaming RPCs, everyday request-response workloads take on all the complexity of streaming. Even for streaming responses, the practical difficulties of working with HTTP trailers have also been apparent for years; I'm shocked that more of the gRPC ecosystem hasn't followed .NET's lead and integrated gRPC-Web support into servers. (If I had to guess, it's difficult because many of Google's gRPC implementations include their own HTTP/2 transport - adding HTTP/1.1 support is a tremendous expansion in scope. Presumably the same applies to HTTP/3, once it's finalized.)

Again, though, I appreciated the inside look into the gRPC team's thinking. It takes courage to discuss the imperfections of your own work, especially when your former coworkers are still supporting the project. gRPC is far from perfect, but the engineers working on it are clearly skilled, experienced, and generally decent people. Hats off to the author - personally, I hope to someday write code influential enough that a retrospective makes the front page of HN :)

0: https://twitter.com/CarlMastrangelo/status/15322565762742435...

1: https://news.ycombinator.com/item?id=31584555

akshayshah··on Why does gRPC insist on trailers?
To be fair, it's also true that Firefox doesn't expose trailers to the fetch API. To the Chrome and gRPC team's credit, the public Chrome issue actually contains a somewhat substantive back-and-forth; the corresponding Firefox issue has virtually no discussion.

https://bugzilla.mozilla.org/show_bug.cgi?id=1339096

akshayshah··on Why does gRPC insist on trailers?
Yes, slightly - RFC 9110 ("HTTP Semantics") calls them "header fields" and "trailer fields," and it calls "headers" and "trailers" colloquialisms. In a nod to gRPC-style usage, the section on trailer fields even says, "Trailer fields can be useful for supplying...post-processing status information."

https://www.rfc-editor.org/rfc/rfc9110.html#header.fields

https://www.rfc-editor.org/rfc/rfc9110.html#trailer.fields

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
It's a variant of the gRPC-Web format that base64-encodes the request and response bodies. It was designed for very old browsers that don't support the fetch API and can't send/receive binary data. All those browsers have been end-of-lifed, and they now represent a tiny fraction of web users.

In short, it's a relic of a bygone age.

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
Timo is amazing, and we wouldn't have been able to build any of this without him :)

We ported the code generators to TypeScript because - as you guessed - it was a bit of a pain to package them for NPM. We also felt that it would be more approachable for TypeScript developers to read, which we hoped would contribute to a sense that all of this isn't actually all that complex. We were a bit worried that performance might be perceivably worse, but the difference wasn't significant.

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
This is - more or less - what the Twirp and Connect protocols are.
akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
Twirp is excellent. Their protocol is _very_ similar to the Connect protocol for unary RPCs.

However, the semantics that Twirp exposes are different from gRPC. That means that it's impossible (or at least _very_ awkward) to have the same server-side code transparently support Twirp and gRPC clients. The Twirp ecosystem is smaller, and has more variable quality, than the gRPC ecosystem; when you choose Twirp for your server, you're betting that you'll never want a client in a language where Twirp is painfully bad.

Our hope is that the Connect protocol appeals to the same folks who like Twirp, and that the server-side Connect implementations make it easier to interop with the gRPC ecosystem.

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
For the most part, we agree! Protobuf isn't perfect. gRPC is _definitely_ optimized for megacorporation-sized problems.

No matter who you are, though, it's not fun to hand-write boilerplate data types and fetch calls or quibble over which parameters should go in the path and which should be query params. Even when you're actually sending JSON on the wire and using regular HTTP/1.1 POSTs, Protobuf and connect-web make it easy to skip right to the interesting part of your code.

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
In many ways, you're spot on. We are trying to build a business: a schema registry for Protobuf. We think that schema-driven APIs makes a lot of sense, that Protobuf is the de facto best schema language, and that the biggest gap is good tooling to share and leverage the schemas.

To retire in Scrooge McDuck-sized vaults of gold,* though, we need to make Protobuf super awesome so that individual developers and companies are willing to use it. It's not super-awesome today, and gRPC is often even worse. To everyone's benefit (I hope), we have the funding and runway to tackle this problem.

From our perspective, your plan looks more like:

(1) Meet everyone where they are, which is mostly Protobuf v3 and gRPC. Adopting a new implementation of a standardized wire protocol in some portion of your system is risky, but not crazy.

(2) Offer a graceful upgrade path to an RPC protocol we think is better, and genuinely solves many pain points with the current status quo.

(3) If we're wildly successful, influence the Protobuf language community whenever a v4 is under discussion. There are certainly warts to fix and nice features to add, but stability and widespread support have a powerful magic of their own.

Good intentions are hard to prove, of course. Hopefully releasing good, well-supported OSS code can be a first step in building some trust.

* Kidding about the piles of gold. I wouldn't say no, but this is the most fun I've had at work in a decade :)

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
The gRPC-Web _protocol_ supports streaming - it supports everything that standard gRPC does.* The client libraries that operate in web browsers don't support client streaming, because every major browser currently buffers request bodies.

Right now, you're correct - the easiest way for another server to speak the Connect protocol is to use connect-go. We're planning to support more backend languages soon: Node.js is up next, with the JVM maybe afterwards. We'd also love to contribute (time, code, review, whatever) to any other backend implementations - the protocol is not that complex. File an issue on one of our projects or ping us in our Slack, it'll probably be an easy sell.

Envoy support is complicated. The honest truth is that we haven't looked into it all that much because it feels rude - Envoy's architecture requires first-party plugins to live in the main repo, and it feels impolite to attempt a C++ code dump for our new project. Maybe once Connect takes the world by storm :) Even if we did do that, a Connect-to-gRPC translation layer would only work for binary payloads - converting JSON to binary protobuf requires the schemas, and redeploying proxies every time the schema changes is an operational nightmare.

* Worth noting that gRPC-Web doesn't really have a specification. There's a narrative description of how gRPC-Web differs from standard gRPC, but they specifically mention that the protocol is defined by the reference implementation in Envoy.

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
Unless you're all-in on server streaming, the Connect protocol is indistinguishable from RESTful HTTP to this class of L7 proxies - it's just a POST to a Protobuf-looking path, with an `application/json` or `application/proto` Content-Type. Most of the proxies/service mesh things that provide rich gRPC instrumentation are also capable of providing rich instrumentation for this style of HTTP.
akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
tRPC is awesome - if you're writing full-stack TypeScript and don't care about other languages, it's very convenient.

Protobuf might be a more appealing choice if you want to support clients in other languages, or if you're interested in getting a bit more value out of your contract language (e.g., breaking change detection).

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
Couldn't agree more, especially about the un-idiomatic libraries in many languages and gRPC's insistence on end-to-end HTTP/2 and trailer support. All of this is, in essence, what we're trying to solve with the Connect family of libraries.

Eventually, we'd like you to have `connect-rails` (or maybe `connect-rack`, I'm an awful Ruby programmer). It would automatically support the gRPC-Web and Connect protocols, and hopefully standard gRPC too (no idea if rails/rack/etc supports trailers). It should feel like writing any other HTTP code, and it should fit right into any existing web application. We'd probably end up writing a new protobuf runtime for Ruby, so the generated classes feel idiomatic. Now your Ruby code is accessible to curl, bash scripts, and plain fetch from the browser, but it's also accessible to a whole ecosystem of clients in other languages - without anybody writing the boring plumbing code over and over.

Protobuf and gRPC aren't perfect, by any stretch, but they're effective. At least IMO, they're also past critical mass in medium-to-large systems: Cap'n Proto _is_ nicer and better-designed, but it doesn't have the adoption and language support that Protobuf has (imperfect as the support may be). Once your backend is gRPC and protobuf, it's really convenient to have that extend out to the front-end and mobile - at least IMO, it's just not worth the effort to hand-convert to RESTful JSON or babysit a grpc-gateway that needs to be redeployed every time you add a field to a message.

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
Probably not - we wrote it alongside connect-web, so it also launched very recently :)

We do think it's the best option for protobuf code generation in TypeScript, though. It comes from quite a few years of experience with proto & TS: the primary author is also the author and maintainer of protobuf-ts, and we had the author of ts-proto review it and give us a thumbs-up before launch.

There _could_ be Bazel rules for it - we certainly write enough other Bazel integrations. Mind filing an issue on https://github.com/bufbuild/protobuf-es?

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
The little demo application is on the connect.build homepage and on https://connect.build/demo. I completely forgot to fix the redirect from demo.connect.build to point to the actual demo page - thank you for reminding me :)

Protocol Buffers are just a schema language. They're spiritually akin to OpenAPI, Thrift, Avro, and a bunch of other contract-type things. They're _also_ a binary serialization format that's usually faster and more compact than JSON. The intention is to use Protocol Buffers as a language-independent way of describing data bags and network APIs, and then use that schema/contract to generate code in the language you actually work in.

It's not that you _can't_ do that with websockets, webrtc, or just RESTful HTTP/1.1 - it's that you shouldn't _need_ to. The hand-written code to define the same data bag classes/interfaces/structs/etc in multiple languages, then wire them up to the same HTTP boilerplate isn't difficult or interesting, it's just toil. For all the times you're just calling an API, Protobuf lets you get on with the interesting work and skip the boilerplate. Ideally, things work well even if you're writing client code in a language that the server team doesn't have any expertise in.

If you're doing something unusual and need webrtc/websockets/etc, go for it!

akshayshah··on Connect-Web: TypeScript library for calling RPC servers from web browsers
From my limited perspective, it'd certainly be nice if browsers exposed more of HTTP. I also appreciate that the web platform is far larger, and far more backward-compatible, than any code I've ever even considered writing. If browser makers are slow to expose new protocol features, that's a cost I'm happy to pay.

At least as I understand it, HTTP Push isn't the problem. The key problem is that browsers don't want to expose HTTP trailers. I understand their reluctance - even putting aside the security concerns, it's a huge semantic change. Many HTTP response headers, like etags, are typically computed by buffering the response, running some compute, then sending headers and the buffered body. All of those would, presumably, now need to also be sendable as trailers. What if the server sends both an etag header and trailer, and they don't match? The rabbit hole goes pretty far. Here's the Chromium issue where the gRPC and Chrome teams debate the issue: https://bugs.chromium.org/p/chromium/issues/detail?id=691599. There's also a series of whatwg issues about exposing trailers in the fetch API: https://github.com/whatwg/fetch/issues/34.

For a protocol that sits above L7, the gRPC HTTP/2 protocol is liberal about diving past the semantics outlined in RFC 9110 and specifying transport-level details. (As an example, trailers-only responses must be sent in a _single_ HTTP/2 headers frame with the EOS bit set.) That degree of specificity unlocks some performance gains, but it makes it near-impossible to speak this protocol using an existing HTTP library - a traditional strength of building on top of L7. For broad adoption in a _lot_ of environments, gRPC-Web (and Connect) are IMO much better protocols.

akshayshah··on Launch HN: Warrant (YC S21) – Authorization and access control as a service
It's cool to see so many authz startups over the past few years! Many established companies have had some bad reliability issues (especially Auth0), and the Zanzibar paper gives that special Google sheen to new products.

Authzed seems to be doing pretty well in this space, but more competition's better for customers. Best of luck!

akshayshah··on Gazette – Build platforms that flexibly mix SQL, batch, and stream processing
The GitHub README and docs mention that Gazette has been running in production for 5 years, but I don’t see any mention of _where_. I assume this began as an internal project at some company - does anyone know which?
akshayshah··on Joseph Weizenbaum's Original Eliza
There's a Go port available over gRPC (and gRPC-Web, and the Connect RPC protocol) at https://demo.connect.build. The code (as well as sample clients) are at https://github.com/bufbuild/connect-demo. You can also talk to our Eliza implementation with cURL:

curl --header 'Content-Type: application/json' --data '{"sentence": "I feel happy."}' https://demo.connect.build/buf.connect.demo.eliza.v1.ElizaSe...

I love that this little bit of computing history is getting some love :)

akshayshah··on RFC 9114 – HTTP/3
This is great! Despite how little the draft has changed over the last year, many communities seemed reluctant to work on an implementation until the RFC was published. Go definitely fell into this camp. Kudos to .NET for shipping HTTP/3 support in Kestrel already!

I have to say that I'm most excited for RFC 9110 (HTTP semantics), though :)

akshayshah··on Connect: A Better gRPC
Fair criticism - you're all the first people to see a lot of this writing, so we're taking notes and we'll try to make the next iteration clearer.

Right now, we've got a Go RPC library that's ready for early adopters. In a month or two, we'll have a TypeScript library for web developers. A while after that, we'll probably tackle server-side TypeScript. We're not sure what will be highest-priority after that: could be Swift + Kotlin for mobile, could be Python, could be something else.

We think that `connect-go` is a better choice than Google's gRPC implementations because it's (1) a simpler implementation, (2) interoperates better with the Go HTTP ecosystem, and (3) supports a nicer protocol _in addition to_ the gRPC and gRPC-Web protocols. You get everything that's good about gRPC, very little (hopefully none!) of what's bad, and you get some extra goodies on top. The same is true of the upcoming `connect-web` compared to `grpc-web`, and so on.

Re: JSON, you're right - proto3 defines a Protobuf-to-JSON mapping that everyone uses. What's less obvious is that if you use JSON with the gRPC protocol, you don't actually end up with JSON HTTP payloads. Instead, you get `<5 bytes of binary framing>{"some": "json"}` - which isn't all that useful, because it's no longer JSON. Then consider that gRPC doesn't use meaningful HTTP status codes, even for request-response RPCs, and it requires using HTTP trailers, and it requires HTTP/2. All of a sudden, it's not much like the ubiquitous, un-fussy HTTP APIs that make REST so successful. The `connect-go` library offers you a solution for this by supporting the Connect _protocol_, which fixes these warts.

Hopefully that's a little clearer <3

akshayshah··on Connect: A Better gRPC
I don't - I've never actually used flatbuffers. There are two pieces of code you'll want (if possible):

1. A `connect.Codec` implementation. I think it'd look almost the same as our Protobuf codec [0], but you'd use `FlatbuffersCodec.Marshal` and `FlatbuffersCodec.Unmarshal` (from github.com/google/flatbuffers/go). You _must_ have a `Codec`, but it looks like it should be pretty quick.

2. Ideally, you'd have a standalone program to parse your Flatbuffer schema and produce Connect code. The output would probably be very similar to `protoc-gen-connect-go`'s [1]. This isn't required, but it's a nice quality of life improvement (check out [1] to see the kind of conveniences it adds). With Protobuf, you'd do this via a `protoc` plugin and you wouldn't need to parse the schema yourself. From a quick look, I don't think `flatc` supports plugins at all - so maybe you'd either skip this step or put in the effort to parse the schema yourself?

[0]: https://github.com/bufbuild/connect-go/blob/main/codec.go#L5...

[1]: https://github.com/bufbuild/connect-go/blob/main/internal/ge...

Also, thank you for making me stop and think about this in detail. I just pulled Flatbuffers out of a hat when I was writing that part of the docs and assumed that `flatc` supported plugins like `protoc`. Turns out that when you assume... I'm updating that portion of the documentation now :)

akshayshah··on Connect: A Better gRPC
I’m tracking a similar issue in https://github.com/bufbuild/connect-go/issues/222 - is this roughly what you’re looking for?
akshayshah··on Connect: A Better gRPC
You found us :) That's the very early version of the marshaling support - it's pure ECMAScript, so it'll work in browsers, Node, or Deno.

`connect-web` will add the RPC layer, built on top of the browser's `fetch` API.

akshayshah··on Connect: A Better gRPC
Basically, yes! Browsers aren't the only clients (or servers) with limited capabilities, but they're probably the most important. Funnily enough, the biggest problem with browsers & gRPC isn't HTTP/2, its support for HTTP trailers.

I'm bemused by gRPC's status code system. It seems just as difficult to use as HTTP status codes, more or less, while also being less widely understood. If I were designing for myself, there would only be four error codes: maybe retriable and definitely not retriable, with an extra bit for whether or not the application returned the code explicitly. Sadly, we had to adopt gRPC's codes to make multi-protocol support work well. (Plus, I don't think anyone else agrees with my hot take on errors!)

akshayshah··on Connect: A Better gRPC
I'm not the greatest wordsmith, and in the rush to write lots of docs for today's launch I let some less-than-clear paragraphs slip through. Sorry! I'll try my best to clarify - appreciate you pointing this paragraph out :)

In general, gRPC documentation is very clear because you're either in or out: if you have end-to-end HTTP/2 and trailer support, you're in and everything works. If you don't have both, you're all the way out and _nothing_ works. Connect aims for something more like progressive enhancement, where implementations support as much of the feature set as they can. Even if that's just unary (request/response) RPCs, it's still useful.

No matter what protocol you're using, bidirectional streaming requires:

1. A schema language with the concept of bidi streaming RPCs. If you're using Thrift, you're out of luck.

2. An HTTP/2 connection. If you're using Python and `requests`, you're out of luck.

If you have both of those, you're capable of bidi streaming. (gRPC's HTTP/2 protocol _also_ requires support for HTTP trailers.)

Protobuf supports bidi streaming RPCs. `connect-go` supports HTTP/2 (and trailers, for the gRPC protocol). So if you're using Protobuf schemas and a `connect-go` client and a `connect-go` server, you have full bidi streaming support and you can choose between any of the three supported protocols. If you're using a `grpc-go` client and a `connect-go` server, or vice versa, you have full bidi streaming support but only using the gRPC HTTP/2 protocol. The wire details differ between the gRPC, gRPC-Web, and Connect protocols, but the semantics are the same and all the Go APIs behave the same.

Hopefully that clarifies things. (If not, let me know and I'll take another run at it!)

akshayshah··on Connect: A Better gRPC
You can use Connect for flatbuffers, too - the gRPC, gRPC-Web, and Connect wire protocols aren't tied to protobuf.

https://connect.build/docs/go/serialization-and-compression#... covers this, and coincidentally uses flatbuffers as the example :)

akshayshah··on Connect: A Better gRPC
That's correct!

Edit: That's _mostly_ correct! You don't need Envoy to have JS/TS running in a browser call your handler. If I'm being really picky, though, in that case the browser and your handler would need to use either the gRPC-Web or Connect protocol, not the backend gRPC-over-HTTP/2 protocol. Any `connect-go` handler you write supports all three protocols by default, so the short answer is still "yes!"

akshayshah··on Connect: A Better gRPC
In theory, there's no reason that grpc-web couldn't support a new protocol. That would be complicated, though; Envoy would also need to support it, and the gRPC team has already designed a protocol that they presumably like.

Connect supports gRPC fully, so you shouldn't ever be locked in - you can always migrate systems to `grpc-go`, one piece at a time. We intend all the Connect projects to be community projects - we're happy to accept contributions, design proposals, and anything else. At the same time, we're committing our resources to keeping these projects well-maintained.

akshayshah··on Connect: A Better gRPC
Twirp _is_ great. For unary RPC, Connect is similar: using the Connect protocol, it works over HTTP/1.1 or HTTP/2, doesn't require any special networking infra, and has first-class support for JSON payloads. In short, `curl | jq` works again.

To us, the biggest difference is that Connect _also_ supports the gRPC and gRPC-Web protocols. That lets your code interop with a much, much larger ecosystem of tools and systems, many of which are better-supported than their Twirp equivalents.

We designed the Connect protocol and the Go APIs to make multi-protocol support seamless. Server-side code supports the Twirp-like protocol and the gRPC protocols simultaneously by default. Clients can switch protocol with one option, no other code changes required.

← PreviousPage 4 of 6Next →