cover

Interacting with the Frontend Using gRPC

sorcererxw

gRPC is a communication protocol widely used in server-side environments. Thanks to Protobuf IDL, we can quickly generate Client and Server Stubs, greatly improving development efficiency and avoiding type errors during communication.

So, can we also apply gRPC to the docking with the front end? The answer is obviously yes, as long as the client can successfully establish an HTTP2 connection with the server, data interaction can be completed on it with the gRPC protocol. However, for web pages, although browsers support HTTP/2, web page code cannot forcibly specify whether a request is going through HTTP/2 or HTTP/1.1, the network bottom layer is shielded by the browser, you can refer to the following article:

The state of gRPC in the browser
A high-performance, open source universal RPC framework
https://grpc.io/blog/state-of-grpc-web/#the-grpc-web-spec
How to implement HTTP/2 stream connection in browser?
Nowadays HTTP/2 is rising as of its performance. The recent version of Node.js supports HTTP/2 very well. https://nodejs.org/api/http2.html But I have no idea how to implement HTTP/2 client in the
https://stackoverflow.com/questions/52273174/how-to-implement-http-2-stream-connection-in-browser

Therefore, if you want to bring the gRPC mechanism to the browser, you need to add an additional protocol conversion layer in the middle to support both HTTP/1.x and HTTP/2 simultaneously.

The more mature solutions currently available are gRPC-Gateway and gRPC-Web:

gRPC-Gateway

gRPC-Gateway is a community-led project that generates an http+JSON gateway server for gRPC by parsing the google.api.http declaration in the Protobuf IDL. Users only need to start this gateway server, and it will receive requests, map them to the corresponding gRPC methods, and call the relevant services.

rpc GetUser(GetUserRequest) returns (GetUserResponse) {
  (google.api.http) = {
    get: "/v1/user/{id}"
  }
}; 

gRPC-Web

The gRPC-Web project is provided by the official gRPC team as a solution for using gRPC in browsers. It is based on the following specifications, performing transcoding between gRPC and gRPC-Web:

grpc/doc/PROTOCOL-WEB.md at master · grpc/grpc
The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#) - grpc/grpc
https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md

Unlike gRPC-Gateway, gRPC-Web does not require additional IDL declarations. Its server still directly receives gRPC requests, but it has made certain adjustments in data encoding and transmission to adapt to HTTP/1.x.

The payload for interaction between the client and server in gRPC-Web can be in binary form, or it can be encoded through BASE64.

curl -v 'http://localhost:8080/api.v1.API/GetInfo' \
  -H 'accept: application/grpc-web-text' \
  -H 'x-grpc-web: 1' \
  -H 'content-type: application/grpc-web-text' \
  --data-raw 'AAAAAD4KPGh0dHBzOi8vdHdpdHRlci5jb20vY29vbFhpYW8vc3RhdHVzLzE0ODIyNTQ3MjU3ODk1NDQ0NDg/cz0yMA==' \
  --compressed

*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> POST /api.v1.API/GetInfo HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept-Encoding: deflate, gzip
> accept: application/grpc-web-text
> x-grpc-web: 1
> content-type: application/grpc-web-text
> Content-Length: 92
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Access-Control-Expose-Headers: Content-Type, Vary, Date, grpc-status, grpc-message
< Content-Type: application/grpc-web-text
< Vary: Origin
< Transfer-Encoding: chunked
<
* Connection #0 to host localhost left intact
AAAAARSKAZACCgpoYXBweSB4aWFvEglAY29vbFhpYW8aRWh0dHBzOi8vcGJzLnR3aW1nLmNvbS9wcm9maWxlX2ltYWdlcy8xMzk4MTQ1NTE0NjY3ODQzNTg2Ly1rNHRheHJMLmpwZyKnAei/meS4que9keermeaPkOS+m+WFjei0ueeahCBtYWNPUyDlupTnlKjlm77moIcKCmh0dHBzOi8vdC5jby9Wd0p2RWVPQndnCgrmiJHmkJzkuobkuIDkuIsgT2JzaWRpYW4g5ZKMIE5vdGlvbgrmnInkupvnmoTnoa7mr5Tljp/phY3nmoTlpb3nnIvwn5mCIGh0dHBzOi8vdC5jby83a1lLSXozTzg3KgYI6PGJjwY=gAAAABBncnBjLXN0YXR1czogMA0K%

Indeed, the direct transmission of binary data makes it difficult for us to debug during development. We cannot understand the content in the payload just by packet capturing, which to some extent reduces the efficiency of development.

However, because the encoding and decoding of payload heavily depend on proto, it also largely prevents others from directly using your interface. You can use the following tool to directly view the content of proto, but you cannot know the meaning of each field, which is better than nothing.

Protobuf Decoder
https://protobuf-decoder.netlify.app/

Implementing Streaming in HTTP/1.x

Unlike HTTP/2, which is a full-duplex network protocol, in HTTP/1.x, the server cannot actively push data to the client. Therefore, there are two ways to implement this:

  • Websocket

    When the service receives a request, it directly negotiates with the client to upgrade the protocol to Websocket, after which data can be freely transmitted.

  • Payload Chunk

    In a response, multiple records are issued, separated by a boundary, which can simulate server streaming.

    As I introduced before, SSE also delivers data in this way, but through a unified standard, the browser has done another layer of encapsulation at the upper level.

In both gRPC-Gateway and gRPC-Web, to implement Server Streaming, they both use the Transfer Encoding Chunk scheme. They will add the header Transfer-Encoding: chunked to the response, so the client will receive the server's data in chunks according to the boundary.

Summary

gRPC-GatewaygRPC-Web
Unary
Server Streaming⚠️ (Requires Base64 encoding)
Client Streaming⚠️ (Requires forwarding mode)
Bi-directional Streaming
Default EncodingJSONProtocol Buffers + Base64
ScenarioSupports both rest and grpc interfaces simultaneously, enabling existing systems to smoothly transition from rest to grpcDirect interaction between front-end and back-end using gRPC protocol, using gRPC-Web to support HTTP/1.x