grpc-rest
Generate 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.
Components
grpc-rest
comes with two main components:
- A protobuf generator plugin that generates Ruby files for Rails routes and controllers.
- The Rails library that powers these generated files.
The protobuf generator uses the same annotations as grpc-gateway. This also gives you the benefit of being able to generate Swagger files by using protoc.
Installation
First, download protoc-gen-rails
from the releases page on the right and unzip it. Ensure the binary is somewhere in your PATH.
Then, add the following to your Gemfile
:
gem 'grpc-rest'
and run bundle install
.
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}"
};
}
}
First, you need to generate the Ruby files from this. 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
- name: rails
out: .
Then, you can run buf generate
to generate the Ruby files. This will generate:
- the Protobuf Ruby files for grpc, in
app/gen
- A new route file, in
config/routes/grpc.rb
- A new controller file, in
app/controllers
.
The generated route file will look like this:
get "example/:name", to: "example#get_example"
and the generated controller will look like this:
require 'services/example/example_services_pb'
class ExampleServiceController < ActionController::Base
protect_from_forgery with: :null_session
METHOD_PARAM_MAP = {
"example" => [
{name: "name", val: nil, split_name:["name"]},
],
}.freeze
rescue_from StandardError do |e|
render json: GrpcRest.error_msg(e)
end
def example
grpc_request = Services::Example::ExampleRequest.new
GrpcRest.assign_params(grpc_request, "/example/{name}", "", request.parameters)
render json: GrpcRest.send_request("Services::Example::ExampleService", "example", grpc_request)
end
end
To power it on, all you have to do is add the following to your config/routes.rb
:
Rails.application.routes.draw do
draw(:grpc) # Add this line
end
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
To Do
- Replace Go implementation with Ruby (+ executable)
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=./protoc-gen-rails/testdata --proto_path=./protoc-gen-rails/google-deps --ruby_out=./spec --grpc_out=./spec ./protoc-gen-rails/testdata/test_service.proto
License
The gem is available as open source under the terms of the MIT License.