Using FauxRPC to debug transcoding HTTP/JSON to gRPC
- 3 minutes read - 472 wordsA Stack overflow question involving transcoding HTTP/JSON to gRPC, piqued my interest. I had a hunch on the solution but was initially dissuaded from attempting a repro because of the complexity:
- recreate proto
- implement stubs
- deploy gRPC-Gateway
I then realized that FauxRPC would probably address much of the complexity and it did.
I created foo.proto
:
syntax = "proto3";
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
// Was not defined in the question.
service Foo {
rpc FetchResource(GetResourceRequest) returns (ResourceResponse) {
option (google.api.http) = {get: "/v1/resource/{resource_id}/group"};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Get Resource from group"
description: "Retrieve resource info"
};
}
};
// Should be named FetchResourceRequest to match the RPC method name.
message GetResourceRequest {
string resource_id = 1 [
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Resource UUID v4."
example: "\"81042622-4f02-4e85-a896-172edd5381b6\""
}
];
ResourceFilter resource_filter = 2 [
(google.api.field_behavior) = OPTIONAL,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "\"RESOURCE_FILTER_FIRST\""
}
];
}
// Empty response for demonstration purposes.
// Was not defined in the question.
// Should be named FetchResourceResponse to match the RPC method name.
message ResourceResponse {}
enum ResourceFilter {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {
title: "Resource Filter"
example: "\"RESOURCE_FILTER_FIRST\""
};
RESOURCE_FILTER_UNSPECIFIED = 0;
RESOURCE_FILTER_FIRST = 1;
}
The proto depends on googleapis
(Google’s repo containing definitions for Google’s services) and grpc-gateway
(containing protoc
plugins and protobuf sources).
My customary approach is to git clone
these sources and then:
# Referenced Protobufs
GOOGLEAPIS="/path/to/googleapis"
GRPC_GATEWAY="/path/to/grpc-gateway"
protoc \
--include_imports \
--include_source_info \
--proto_path=${GOOGLEAPIS} \
--proto_path=${GRPC_GATEWAY} \
--proto_path=${PWD} \
--descriptor_set_out=${PWD}/foo.binpb \
${PWD}/foo.proto
Then, it’s straightforward to install and run FauxRPC:
go install github.com/sudorandom/fauxrpc/cmd/fauxrpc@latest
fauxrpc run \
--schema=${PWD}/foo.binpb \
--dashboard
2025/10/10 09:36:56 INFO fieldByName name=GetResourceRequest name=resource_id
FauxRPC (v0.16.0) - 1 services loaded, 0 stubs loaded
Listening on http://127.0.0.1:6660
Dashboard: http://127.0.0.1:6660/fauxrpc
OpenAPI documentation: http://127.0.0.1:6660/fauxrpc/openapi.html
Example Commands:
$ buf curl --http2-prior-knowledge http://127.0.0.1:6660 --list-methods
$ buf curl --http2-prior-knowledge http://127.0.0.1:6660/[METHOD_NAME]
2025/10/10 09:36:56 INFO Server started.
FauxRPC generates OpenAPI documentation on http://127.0.0.1:6660/fauxrpc/openapi.html#/
In this configuration, this includes an ENDPOINT Foo
(reflecting the protobuf service
name) which includes the FetchResource
rpc mapped, per the google.api.http
annotation, to a GET
:
What’s excellent is, not only, does the documentation include an example curl
command but the documentation correctly documents the ‘Query Parameter(s)’ of resourceFilter
and its allowed values e.g. RESOURCE_FILTER_FIRST
.
Reassuringly this validated my hunch about the correct URL and, now with a FauxRPC server, we can attempt to invoke it:
Either append ?
and the query string directly to the URL:
curl \
--silent \
--request GET \
--header "Accept: application/json" \
--url "http://127.0.0.1:6660/v1/resource/{resource_id}/group?resourceFilter=RESOURCE_FILTER_FIRST"
Yields {}
(200)
Or use --get
(instead of --request GET
) and --data-urlencode
to URL encode the query string
curl \
--silent \
--get \
--header "Accept: application/json" \
--data-urlencode "resourceFilter=RESOURCE_FILTER_FIRST" \
--url "http://127.0.0.1:6660/v1/resource/{resource_id}/group" \
--output /dev/null \
--write-out "%{response_code}\n"
Yields 200
(success)
For each invocation, FauxRPC should log:
[] 2025/10/10 - 09:52:06 | 200 | 728.247µs | 127.0.0.1 | GET "/v1/resource/resource_id/group?resourceFilter=RESOURCE_FILTER_FIRST"