grpc-rest
Generate dynamic Rails routes and controllers from protobuf files.
grpc-rest allows you to have a single codebase that serves both gRPC and classic Rails routes, with a single handler (the gRPC handler) for both types of requests. It will add Rails routes to your application that maps JSON requests to gRPC requests, and the gRPC response back to JSON. This is similar to grpc-gateway, except that rather than proxying the gRPC requests, it simply uses the same handling code to serve both types of requests.
In order to actually accept both gRPC and REST calls, you will need to start your gRPC server in one process or thread, and start your Rails server in a separate process or thread. They should both be listening on different ports ( the default port for Rails is 3000 and for gRPC is 9001).
With this, you get the automatic client code generation via Swagger and gRPC, the input validation that's automatic with gRPC, and the ease of use of tools like curl
and Postman with REST.
Upgrading to 0.5
Before 0.5, you needed to generate Rails controller and route files. As of 0.5, these are no longer necessary and you can remove the following:
- Generated files in
app/controllers
- Generated files in
config/routes
- The
draw(:grpc)
line inconfig/routes.rb
- Add
require 'grpc_rest'
to yourconfig/application.rb
Installation
Add the following to your Gemfile
:
gem 'grpc-rest'
and run bundle install
.
Then add this to your config/application.rb
:
require 'grpc_rest'
Routes
grpc-rest
uses the same annotations as grpc-gateway to define routes. This also gives you the benefit of being able to generate Swagger files by using protoc.
Example
Here's an example protobuf file that defines a simple service:
syntax = "proto3";
package example;
import "google/api/annotations.proto";
message ExampleRequest {
string name = 1;
}
message ExampleResponse {
string message = 1;
}
service ExampleService {
rpc GetExample (ExampleRequest) returns (ExampleResponse) {
option (google.api.http) = {
get: "/example/{name}"
};
}
}
You need to generate the Ruby files from this, the same way you would for any Protobuf Ruby code. You can do this with plain protoc
, but it's much easier to handle if you use buf. Here's an example buf.gen.yaml
file:
version: v1
managed:
enabled: true
plugins:
- plugin: buf.build/grpc/ruby:v1.56.2
out: app/gen
opt:
- paths=source_relative
- plugin: buf.build/protocolbuffers/ruby:v23.0
out: app/gen
opt:
- paths=source_relative
Then, you can run buf generate
to generate the Ruby files.
The dynamic route will act like this:
get "example/:name", to: "example#get_example"
Caveats
This gem does not currently support the full path expression capabilities of grpc-gateway or the Google http proto. It only supports very basic single wildcard globbing (*
). Contributions are welcome for more complex cases if they are needed.
Proto Options
By default, grpc-rest uses the Protobuf default of omitting empty values on response. You can change this behavior by using an OpenAPI extension with the key x-grpc-rest-emit-defaults
:
service MyService {
rpc Test(MyRequest) returns (MyResponse) {
option (google.api.http) = {
get: "/test/"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
extensions: {
key: 'x-grpc-rest-emit-defaults';
value: {
bool_value: true;
}
}
};
}
}
Gruf Interceptors
grpc-rest supports gruf Interceptors through a custom GrpcRest::BaseInterceptor
class. As long as you're not using a custom interceptor
registry, your interceptors will be called normally around the controller.
require 'grpc_rest/base_interceptor'
module Interceptors
# Interceptor for catching errors from controllers
class ErrorInterceptor < GrpcRest::BaseInterceptor
def call
# Your code here
end
end
end
Configuration
If you're using gruf
, grpc-rest will automatically collect the known Gruf controllers. If you're not using it, you have to tell grpc-rest which services you want to generate for by setting:
GrpcRest.services = [
Example::ExampleService::Service
]
GrpcRest currently has one configuration option, strict_mode
, which defaults to false. When set to true, any JSON request that includes an unknown field will be rejected.
GrpcRest.strict_mode = true
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/flipp-oss/grpc-rest.
To regenerate Ruby protobuf code for tests, install the grpc-tools
gem and run this from the base directory:
grpc_tools_ruby_protoc -I=./spec/testdata --proto_path=./spec/google-deps --ruby_out=./spec --grpc_out=./spec ./spec/testdata/test_service.proto
License
The gem is available as open source under the terms of the MIT License.