背景
gRPCを直接たたけないclientがいるので、HTTPリクエストを変換してrpcをinvokeするのを試す
インストール
Mac:
brew install envoy
Other: https://www.envoyproxy.io/docs/envoy/latest/start/install#
Version
envoy --version
envoy version: c919bdec19d79e97f4f56e4095706f8e6a383f1c/1.22.2/Modified/RELEASE/BoringSSL
gRPC-JSON transcoder
1. Protobuf作成
syntax = "proto3";
package helloworld;
option go_package = "github.com/nakamasato/envoy-training/grpc-json";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello(HelloRequest) returns (HelloReply) {
}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
2. bufでコード生成
buf.yaml
version: v1
name: buf.build/nakamasato/grpc-json
buf.gen.yaml
version: v1
plugins:
- name: go
out: .
opt: paths=source_relative
- name: go-grpc
out: .
opt: paths=source_relative,require_unimplemented_servers=false
buf generate
これで、helloworld_grpc.pb.go と helloworld.pb.go が生成される
3. main.go作成
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
helloworldpb "github.com/nakamasato/envoy-training/grpc-json/protos"
)
type server struct {
helloworldpb.UnimplementedGreeterServer
}
func NewServer() *server {
return &server{}
}
func (s *server) SayHello(ctx context.Context, in *helloworldpb.HelloRequest) (*helloworldpb.HelloReply, error) {
return &helloworldpb.HelloReply{Message: in.Name + " world"}, nil
}
func main() {
// Create a listener on TCP port
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln("Failed to listen:", err)
}
// Create a gRPC server object
s := grpc.NewServer()
// Attach the Greeter service to the server
helloworldpb.RegisterGreeterServer(s, &server{})
// Serve gRPC Server
log.Println("Serving gRPC on 0.0.0.0:50051")
log.Fatal(s.Serve(lis))
}
4. gRPC server起動
go run main.go
5. grpcurlでコール
grpcurl -d '{"name": "naka"}' -import-path ./protos -proto protos/helloworld.proto -plaintext localhost:50051 helloworld.Greeter/SayHello
{
"message": "naka world"
}
6. protoの更新
以下を追加:
import "google/api/annotations.proto";
option (google.api.http) = {
post: "/say"
body: "*"
};
最終的なprotoはこちら
helloworld.proto
syntax = "proto3";
package helloworld;
import "google/api/annotations.proto";
option go_package = "github.com/nakamasato/envoy-training/grpc-json";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello(HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post : "/say"
body : "*"
};
}
}
// The request message containing the user's name.
message HelloRequest { string name = 1; }
// The response message containing the greetings
message HelloReply { string message = 1; }
7. bufでコード生成
buf.yaml
version: v1
name: buf.build/nakamasato/grpc-json
deps:
- buf.build/googleapis/googleapis
buf mod update
buf build --as-file-descriptor-set -o helloworld.pb
grpc-json transcoderがproto descriptorを必要とするのでoutputしておく
8. grpcurlとproto descriptorで確認
grpcurl -d '{"name": "naka"}' -protoset protos/helloworld.pb -plaintext localhost:50051 helloworld.Greeter/SayHello
9. envoy config準備
ほぼsample-exampleを使用
admin:
address:
socket_address: {address: 0.0.0.0, port_value: 9901}
static_resources:
listeners:
- name: listener1
address:
socket_address: {address: 0.0.0.0, port_value: 51051}
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: grpc_json
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
# NOTE: by default, matching happens based on the gRPC route, and not on the incoming request path.
# Reference: https://envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_json_transcoder_filter#route-configs-for-transcoded-requests
- match: {prefix: "/helloworld.Greeter"}
route: {cluster: grpc, timeout: 60s}
http_filters:
- name: envoy.filters.http.grpc_json_transcoder
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
proto_descriptor: "protos/helloworld.pb"
services: ["helloworld.Greeter"]
print_options:
add_whitespace: true
always_print_primitive_fields: true
always_print_enums_as_ints: false
preserve_proto_field_names: false
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: grpc
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
dns_lookup_family: V4_ONLY
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
load_assignment:
cluster_name: grpc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: localhost
port_value: 50051
10. envoy起動
envoy -c envoy.yaml
11. HTTP requestを確認
curl -d '{"name": "naka"}' http://localhost:51051/say
{
"message": "naka world"
}