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