gRPC-Web w/ FauxRPC and Rust
- 4 minutes read - 719 wordsAfter recently discovering FauxRPC, I was sufficiently impressed that I decided to use it to test Ackal’s gRPC services using rust.
FauxRPC provides multi-protocol support and so, after successfully implementing the faux gRPC client tests, I was compelled to try gRPC-Web too. For no immediate benefit other than, it’s there, it’s free and it’s interesting. As an aside, the faux REST client tests worked without issue using Reqwest.
Unfortunately, my optimism hit a wall with gRPC-Web and what follows is a summary of my unresolved issue.
After banging my head against the wall with the faux Ackal gRPC-Web client, I realized that tonic-web
includes a “definitive” client and server implementation of gRPC-Web that works for me.
server
:
cargo run \
--package=examples \
--bin=grpc-web-server \
--features=grpc-web
GreeterServer listening on 127.0.0.1:3000
And:
client
:
cargo run \
--package=examples \
--bin=grpc-web-client \
--features=grpc-web
RESPONSE=Response {
metadata: MetadataMap {
headers: {
"content-type": "application/grpc-web+proto",
"vary": "origin,access-control-request-method, access-control-request-headers",
"transfer-encoding": "chunked",
"date": "Wed, 13 Nov 2024 19:48:16 GMT",
"grpc-status": "0"
}
},
message: HelloReply {
message: "Hello Freddie!"
},
extensions: Extensions
}
Great!
But, if I switch to FauxRPC, I’m able to reproduce the error I saw from the Ackal gRPC-Web client:
PORT="6660"
fauxrpc run \
--addr=localhost:${PORT} \
--schema=helloworld.binpb
Error: Status { code: Unknown, message: " ", source: None }
The helloworld.binpb
schema for FauxRPC was generated with:
# Path to tonic repo's examples folder
PROTOS="${PWD}/protos"
protoc \
--proto_path=${PROTOS} \
--descriptor_set_out=helloworld.binpb \
--include_imports \
${PROTOS}/helloworld/helloworld.proto
What’s frustrating is that both buf curl
and Kreya (used because it supports gRPC and gRPC-Web unlike my preferred gRPCurl).
PORT="6660" # FauxRPC
buf curl \
--verbose \
--protocol=grpcweb \
--http2-prior-knowledge \
--schema=helloworld.binpb \
http://localhost:${PORT}/helloworld.Greeter/SayHello
buf: * Invoking RPC helloworld.Greeter.SayHello
buf: * Dialing (tcp) localhost:12345...
buf: * Connected to 127.0.0.1:12345
buf: > (#1) POST /helloworld.Greeter/SayHello
buf: > (#1) Accept-Encoding: identity
buf: > (#1) Content-Type: application/grpc-web+proto
buf: > (#1) Grpc-Accept-Encoding: gzip
buf: > (#1) Grpc-Timeout: 119963m
buf: > (#1) User-Agent: grpc-go-connect/1.17.0 (go1.23.1) buf/1.45.0
buf: > (#1) X-User-Agent: grpc-go-connect/1.17.0 (go1.23.1)
buf: > (#1)
buf: } (#1) [5 bytes data]
buf: * (#1) Finished upload
buf: < (#1) HTTP/2.0 200 OK
buf: < (#1) Content-Type: application/grpc-web+proto
buf: < (#1) Date: Wed, 13 Nov 2024 19:54:32 GMT
buf: < (#1)
buf: { (#1) [5 bytes data]
buf: { (#1) [9 bytes data]
buf: { (#1) [5 bytes data]
buf: { (#1) [32 bytes data]
buf: * (#1) Call complete
{
"message": "Freddie"
}
In summary, I’ve a single (reproducible) permutation in which I don’t appear (!?) to be receiving a correct gRPC-Web response:
Client | Server | |
---|---|---|
client.rs |
FauxRPC | ❎ |
client.rs |
server.rs |
✅ |
buf curl |
server.rs |
✅ |
buf curl |
FauxRPC | ✅ |
Kreya | server.rs |
✅ |
Kreya | FauxRPC | ✅ |
I monitored the requests being sent to FauxRPC by each of the clients and, while there are minor (header) differences and a use of chunking sometimes (relevant?), there was nothing obvious that corresponded to the client.rs
issue.
What I really want to do is debug client.rs
to determine what response it’s receiving and how this flows through the code. This was challenging and I’ve not pursued it.
Instead I decided to use socat
to proxy/log traffic:
PORT=6660 # FauxRPC
socat -v TCP-LISTEN:12345,fork,reuseaddr TCP:localhost:${PORT}
For the tonic-web
failing client.rs
request:
> 2024/11/13 12:12:49.000320511 length=171 from=0 to=170
POST /helloworld.Greeter/SayHello HTTP/1.1\r
te: trailers\r
content-type: application/grpc-web\r
host: localhost:12345\r
transfer-encoding: chunked\r
\r
E\r
....
\aFreddie\r
0\r
\r
< 2024/11/13 12:12:51.000759623 length=145 from=0 to=144
HTTP/1.1 200 OK\r
Content-Type: application/grpc-web+proto\r
Date: Wed, 13 Nov 2024 20:12:51 GMT\r
Transfer-Encoding: chunked\r
\r
e\r
....
\aFreddie\r
< 2024/11/13 12:12:51.000760926 length=48 from=145 to=192
25\r
.... Grpc-Message: \r
Grpc-Status: 0\r
\r
0\r
\r
For the Kreya succeeding request:
> 2024/11/13 12:14:40.000795916 length=216 from=0 to=215
POST /helloworld.Greeter/SayHello HTTP/1.1\r
Host: localhost:12345\r
TE: trailers\r
grpc-accept-encoding: identity,gzip,deflate\r
Transfer-Encoding: chunked\r
Content-Type: application/grpc-web\r
\r
E\r
....
\aFreddie\r
0\r
\r
< 2024/11/13 12:14:42.000968834 length=145 from=0 to=144
HTTP/1.1 200 OK\r
Content-Type: application/grpc-web+proto\r
Date: Wed, 13 Nov 2024 20:14:42 GMT\r
Transfer-Encoding: chunked\r
\r
e\r
....
\aFreddie\r
< 2024/11/13 12:14:42.000970169 length=48 from=145 to=192
25\r
.... Grpc-Message: \r
Grpc-Status: 0\r
\r
0\r
\r
Can you see the difference? No, me either :-(
Well, there are differences
Property | client.rs |
Freya | Explanation |
---|---|---|---|
Request length | 171 | 145 | Explained by the 45 character grpc-accept-encoding header |
Headers | lowercase | uppercase | Should be inconsequential |
grpc-accept-encoding |
omitted | present | It’s possible to replicate by revising client.rs but makes no difference |
Both responses include Freddie
and gRPC status code of 0
(OK)
Stumped :-(