The original address: www.improbable.io/blog/grpc-w…

Original author:

Published time: April 26, 2017

Michal Witkowski is Improbable’s principal technology lead on SpatialOS, and Marcus Longmuir is Improbable Webtools’ technical lead.

REST+JSON is the de facto standard way to build interactions between Web applications and API servers. However, once you get past the initial simple prototyping stage, it shows up maintaining hand-crafted client code, debugging network protocol issues, and a lack of type safety. This post describes how we can extend the use of gRPC, the common language for microservices and client libraries, to use in browser Web Apps. We’ll also show how this move, and our adoption of TypeScript, has led to our eventual nirvana in type-safe Web apps.

Narrator: Why did you use gRPC?

At Improbable, we are building SpatialOS, a PaaS product for space-aware simulation that leverishes existing simulation models, a game engine and a visualization client written in multiple languages. Early in the development of our platform, we decided on the importance of a strongly typed, well-documented API. Initially, we chose REST and HAL, but the burden of re-implementing and maintaining clients for numerous languages (C++, Java, Go, C#, Objective-C) began to eat into our time doing what we really enjoyed, which was solving our customers’ real problems, so we decided to look elsewhere.

Around the same time, Google opened source gRPC, their internal next-generation RPC library based on the then-new HTTP2 protocol, supporting multiple languages (C++, Java, Go, C#, Objective-C, NodeJS……). Code to generate client and server stubs. The latter completely meets our requirements, and after preliminary exploration and verification, we decided to adopt it company-wide. Soon, the.PROto definitions that provided the patterns for gRPC services became the standard way to think about services: they were the first things we came together in design discussions, and the normative documentation of how things worked.

This allows teams with different areas of expertise (SDK, backend, CLI) and different language backgrounds to express contracts and expectations in a single way. By taking advantage of protocol buffer compatibility, “Do I need to add this field at all?” “Or” What does this field accept? The problem no longer appears.

The Web is an exception

The Web application team is an exception to this cultural shift by not supporting gRPC from the browser. Also, their intent was to consume the same gRPC platform apis as our clients and CLI tools, so we needed to find a way to expose them.

Fortunately, with the gRPC REST Gateway, there is a code generator for a REST+JSON API binding based on the gRPC apis annotated in the.proto file. The approach we take is very similar to the one described here.

While this solution eliminates the need to manually write API server-side code, it still has a number of drawbacks. Our Web App team still had to write JSON parsing code and client-side object representations, write HTTP client libraries with the right calling conventions, and occasionally peek into the browser’s Network Console to find a weird Network error. But more importantly: The new API (methods, fields) is only visible to Web Apps when the platform API is regenerated and redeployed with the gRPC REST gateway. This is a huge hurdle for an engineering organization doing independent and frequent launches.

Hacker and non-hacker archetypes.

We started thinking about removing the gRPC REST gateway completely from our API stack. It does two main things: translate Protobuf messages into JSON and provide an HTTP/1.1 compliant API and optional RESTful semantics.

Which brings us to: modern browsers are very…… Modern. They can of course handle protobuf of raw bytes, not just JSON, and they will also say HTTP2. Why can’t browsers speak protobuf binary, as well as http2-based gRPC itself?

So, near the end of the sunny London summer of 2016, we decided to get things done: using the official Proto3 JavaScript CodeGen, a clean revamp of the Fetch API, plus a bit of HTTP2 hacking on the Go side…… We have a browser say gRPC to a GO-based gRPC server.

Later we learned that the gRPC team at Google was developing the GRPC-Web specification internally. Their approach was so similar to ours that we decided to contribute our experience to the upstream GRPC-Web specification (currently in early access mode and still subject to change).

At the same time, we are rebasing all Web applications on TypeScript. While the initial spike confirmed the value of this effort, it was only after we completed the migration that we realized that we had underestimated the long-term productivity gains compile-time type validation would bring to our Web application team.

Grpc-web for TypeScript and Go.

So at this point, we’re ready to pull the trigger and turn our prototype into production. Our goal is simple: make it easy to use the gRPC APIs (written mostly in Go) in modern type-safe Web apps (mainly TypeScript).

We built a production-ready implementation with four components necessary to achieve this goal.

  • Grpcweb – A Go package that wraps around an existing Go gRPC server and makes it grPC-Web.
  • Grpcwebproxy – a grpc-web independent reverse proxy (such as Java or C++) for a traditional gRPC server.
  • Ts-protoc-gen – TypeScript plug-in for the Protocol buffering compiler (PROTOC), which generates TypeScript service definitions and TypeScript declarations for standard JavaScript objects generated by upstream Protocs.
  • Grpc-web-client – A TypeScript GRPC-Web client library for browsers that abstracts the web (Fetch API or XHR) from user and code-generated classes.

With these components, the.proto definition of our service automatically generates TypeScript objects needed to use our platform API, and all TypeScript type-safety glory is retained from the.proto file. In addition, GRPC-Web-client is intended for backward compatibility. Not only does it support non-FETCH API browsers, but it can fall back to HTTP1.1 if needed. So far our test matrix goes all the way back.

  • IE10+
  • Edge 13+
  • Firefox 38+
  • Chrome 41+
  • Safari 8+

Draw me a code diagram

Suppose you use the following.proto file to define an RPC and a server flow RPC for a hypothetical BookService.

import pb_library ".. /_proto/examplecom/library"

type bookService struct{
books []*pb_library.Book
}

func (s *bookService) GetBook(ctx context.Context, bookQuery *pb_library.GetBookRequest) (*pb_library.Book, error) {
    for _, book := range s.books {
        if book.Isbn == bookQuery.Isbn {
            return book, nil}}return nil, grpc.Errorf(codes.NotFound, "Book could not be found")}func (s *bookService) QueryBooks(bookQuery *pb_library.QueryBooksRequest, stream pb_library.BookService_QueryBooksServer) error {
    for _, book := range s.books {
        if strings.HasPrefix(s.book.Author, bookQuery.AuthorPrefix) {
            stream.Send(book)
        }
    }
    return nil
}
Copy the code

After the code generates JavaScript/TypeScript messages and method stubs, you can call the code from the browser using the following code sample snippet.

import {grpc, BrowserHeaders} from "grpc-web-client";

// Import code-generated data structures.
import {BookService} from ".. /_proto/examplecom/library/book_service_pb_service";
import {QueryBooksRequest, Book, GetBookRequest} from ".. /_proto/examplecom/library/book_service_pb";

const queryBooksRequest = new QueryBooksRequest();
queryBooksRequest.setAuthorPrefix("Geor");

grpc.invoke(BookService.QueryBooks, {
  request: queryBooksRequest,
  host: "https://my.grpc.server.example.com".onMessage: (message: Book) = > {
    console.log("got book: ", message.toObject());
  },
  onEnd: (code: grpc.Code, msg: string | undefined, trailers: BrowserHeaders) = > {
    if code == grpc.Code.OK { console.log("all ok")}else { console.log("hit an error", code, msg, trailers); }}});Copy the code

The future of Web App development

Although our motivation for developing GRPC-Web was primarily to eliminate the need to constantly rebuild and redeploy the gRPC REST gateway, we think we have stumbled upon a potential Web App development game. Our experience with gRPC Web was transformative for our Web App team.

  • No longer need to look for API documentation — ProTO is the standard format for API contracts, as it is for other teams
  • No more hand-crafted JSON call objects — all requests and responses are strongly typed and code-generated, with hints in the IDE.
  • No more processing methods, headers, principals, and low-level networks are needed — everything is handled and abstracted in GRPC-Web-client.
  • No more guesswork on the meaning of HTTP error codes –gRPC status codes are a canonical way of expressing problems in apis.
  • No more hand-crafted bulk coding streaming frenzy on the server, GRPC-Web supports 1:1 RPC and 1: many server-side streaming requests.
  • When new binaries are rolled out, there are no data parsing errors — the protocol buffer guarantees the compatibility of requests and responses.

In short, the gRPC Web moves the interaction between front-end code and microservices from the realm of hand-crafted HTTP requests to a well-defined user logic approach. This was a huge boon to the productivity of our Web App team: they could focus on building valuable client logic instead of hand-crafting REST clients.

Open Source and contributions

In recognition of the contribution open source has made to Improbable’s success, we decided to do more than just give back through bug reports and patches. Improbable Engineering now has a GitHub organisation, Improbable – ENG, where we will open source our interesting technology, Improbable – Eng/GRPC-Web is just one of them.

It’s worth noting that Improbable – ENG /grpc-web is not a formal implementation. However, we look forward to working with the gRPC team at the Cloud Native Computing Foundation (CNCF) and contributing the TypeScript protocol buffer plug-in, the TypeScript gRPC client library, and the Go middleware upstream.

Sound like fun? Check out our current engineering roles, including the Web and infrastructure team that built the GRPC-Web library.

We manufacture SpatialOS – learn more about our platform.


Translation via www.DeepL.com/Translator (free version)