Google announced gRPC back in February and everyone was excited about it. It is a RPC framework using new ProtocolBuffer3 based on HTTP/2 as transport. Because it is RPC framework, user should look into its API and ProtocolBuffer basics. But as HTTP/2 implementor, we are interested in how HTTP/2 is used under the hood. So we decided to proxy the communication between gRPC client and server with nghttpx proxy server and see what’s going on in transport level.
We used C++ client and server pulled from
grpc-common. Build everything
as instructed in README. We made one modification to the
greeter_client.cc to change remote port number to 3000 to connect to
nghttpx proxy. We used nghttpx --http2-bridge
mode.
The configuration was like this:
1 2 3 4 5 6 |
|
Then we observed the traffic between greeter_client and greeter_server.
greeter_client sent the following request header fields:
1 2 3 4 5 6 |
|
This is the usual HTTP/2 request, but one thing to note is te: trailers
.
According to
RFC 7230, section 4.3.:
The “TE” header field in a request indicates what transfer codings, besides chunked, the client is willing to accept in response, and whether or not the client is willing to accept trailer fields in a chunked transfer coding.
HTTP/2 disallows transfer-encoding stuff, but it only allows “trailers” in “TE” header field:
The presence of the keyword “trailers” indicates that the client is willing to accept trailer fields in a chunked transfer coding, as defined in Section 4.1.2, on behalf of itself and any downstream clients.
So in HTTP/1.1, trailer fields are only allowed in chunked transfer encoding. In HTTP/2, chunked transfer encoding is deprecated because it has more elegant framing and thanks to it, trailer fields are always allowed.
gRPC does not work without “TE” header field.
The request body was like this (in hexdump):
1 2 |
|
This would be data serialized by ProtocolBuffer. We don’t go down the detail in ProtocolBuffer here.
nghttpx forwarded this requests to greeter_server with additional header fields, like “Via” header field.
Upon receiving request, greeter_server processed the request and responded with the following response header fields:
1
|
|
Well, that’t the most simple HTTP/2 response, followed by response body (again, in hexdump):
1 2 3 |
|
Then server sent trailer fields:
1 2 |
|
nghttpx forwarded this reponse to the greeter_client and it shows the result to the console:
1
|
|
So in tranport wise, gRPC uses POST request and response uses trailer fields. This means that we can proxies the gRPC traffic using HTTP/1.1 path as well, since we can forward it in chunked transfer encoding. This just comes from our curiosity and we have no idea this is useful in practice. But this shows the great effort we put in HTTP/2 to interoperate with existing HTTP/1.1 infrastructure.
To prove this, we added another nghttpx to set up HTTP/1.1 path:
1 2 3 4 5 6 |
|
And this works! nghttpx[1] forwarded gRPC request in HTTP/1.1 chunked transfer-encoding:
1 2 3 4 5 6 7 |
|
In response chain, nghttpx[2] forwarded response header to nghttpx[1]:
1 2 3 4 |
|
… with trailer fields appended after the last chunk.