在网格中使用gRPC可以方便的定义服务,而当需要对外提供服务时往往HTTP接口比较普遍,grpc-gateway提供了RESTful JSON APIgRPC的代理服务。 在meshgrpc-gateway的角色更像一个服务聚合层,将内部gRPC服务聚合并提供HTTP接口服务,在使用的过程中也遇到些问题,如:进程内路由、Header映射等,这里结合使用经验做个总结。

接口定义

gRPC的服务定义这里不做过多关联介绍,grpc-gateway协议支持两种方式添加,一是在.proto的服务定义中以option的方式直接绑定; 二是使用单独的.yaml文件定义。接口定义遵循google.api大家可以参考, 主要内容在HttpRule,具体字段参考协议http.proto更清晰, 比如:response_body在文档中并没有提到,用于选择Response结构的某一字段作为响应内容。

syntax = "proto3";
package example;

import "google/api/annotations.proto";

message StringMessage {
    string value = 1;
}

service YourService {
    rpc Echo (StringMessage) returns (StringMessage) {
       option (google.api.http) = {
           post: "/v1/example/echo"
           body: "*"
       };
    }
}
type: google.api.Service
config_version: 3

http:
  rules:
  - selector: example.YourService.Echo
    post: /v1/example/echo
    body: "*"

进程内路由

grpc-gateway grpc-gateway的代理方式是gRPC远程调用,在mesh中有已经有ingress网关,如果继续用rpc调用, 将在链路上又增加一层网络开销ingress -> grpc-gatewap -> grpc server,而我们的主要目的是将gRPC服务映射成HTTP并不需要其他能力, 所以我们期望grpc-gateway能够进程将HTTP请求转发给gRPC服务。

针对这一需求需要gRPC服务支持进程内调用,社区现有的方案是test/bufconn, 示例参考grpc-gateway/pull/947中的讨论。 至于直接进程内调Contributor的最新回复是8月份启动grpc-go/issues/906

另一个临时的方案是我提交到grpc-gatewayPR pull/947,已经mergemaster, 但此方案仅支持Unary,不支持Streaming,不过对于网关应该能够满足大部分场景的需求。

func RegisterXXXHandlerServer(ctx context.Context, mux *runtime.ServeMux, server XXXServer) error

Header传递

tracing等场景我们往往需要将Header信息在服务之间传递,包括RESTful API请求。首先grpc-gatewayNewServeMux()需要runtime.WithMetadata()Option, 筛选HTTP Header映射为metadata;其次如果是使用test/bufconn方案, 在RegisterXXXHandlerFromEndpoint(...,opts []grpc.DialOption) optsgrpc.WithChainUnaryInterceptor()中需要增加metadata的映射。

grpc-gateway的另一个方式是runtime.WithIncomingHeaderMatcher()

为了方便使用可以参考我的metadata插件github.com/hb-go/grpc-contrib/metadata, 支持指定Header前缀匹配

hb-go/grpc-contrib/metadata示例

mux := runtime.NewServeMux(
    runtime.WithMetadata(metadata.GatewayMetadataAnnotator(
        metadata.WithHeader("x-request-id"),
        metadata.WithPrefix("x-prefix"),
    )),
)
err := pb.RegisterXXXHandlerFromEndpoint(ctx, mux, "",
    []grpc.DialOption{
        grpc.WithChainUnaryInterceptor(
            metadata.UnaryClientInterceptor(
                metadata.WithHeader("x-request-id"),
                metadata.WithPrefix("x-prefix"),
            ),
        ),
    },
)

Istio环境要完整tracing链路,ClientConn需要与grpc-gateway一样,在DialOptiongrpc.WithChainUnaryInterceptor()中加入metadata.UnaryClientInterceptor(...)Header说明参考Istio文档WHAT IS REQUIRED FOR DISTRIBUTED TRACING WITH ISTIO?

metadata.WithHeader("x-request-id"),
metadata.WithHeader("x-b3-traceid"),
metadata.WithHeader("x-b3-spanid"),
metadata.WithHeader("x-b3-parentspanid"),
metadata.WithHeader("x-b3-sampled"),
metadata.WithHeader("x-b3-flags"),
metadata.WithHeader("b3"),
metadata.WithHeader("x-ot-span-context"),